From 4f25b0fd1299a3359d15cf929347924f8609edb4 Mon Sep 17 00:00:00 2001 From: devin ivy Date: Mon, 5 Feb 2024 15:05:31 -0500 Subject: [PATCH 001/103] Track blob cids on ozone mod events (#2135) track blob cids on ozone mod events --- .../src/api/admin/emitModerationEvent.ts | 14 ++-- .../20240201T051104136Z-mod-event-blobs.ts | 15 ++++ packages/ozone/src/db/migrations/index.ts | 1 + .../ozone/src/db/schema/moderation_event.ts | 1 + packages/ozone/src/db/types.ts | 7 +- packages/ozone/src/mod-service/index.ts | 24 ++++-- packages/ozone/src/mod-service/status.ts | 5 +- packages/ozone/src/mod-service/subject.ts | 11 ++- packages/ozone/src/mod-service/views.ts | 2 +- .../ozone/tests/moderation-events.test.ts | 75 +++++++++++++++++-- .../ozone/tests/moderation-statuses.test.ts | 55 ++++++++++++++ 11 files changed, 183 insertions(+), 27 deletions(-) create mode 100644 packages/ozone/src/db/migrations/20240201T051104136Z-mod-event-blobs.ts diff --git a/packages/ozone/src/api/admin/emitModerationEvent.ts b/packages/ozone/src/api/admin/emitModerationEvent.ts index eb1bc71a180..bc198398013 100644 --- a/packages/ozone/src/api/admin/emitModerationEvent.ts +++ b/packages/ozone/src/api/admin/emitModerationEvent.ts @@ -51,17 +51,21 @@ export default function (server: Server, ctx: AppContext) { } if (isTakedownEvent || isReverseTakedownEvent) { - const isSubjectTakendown = await moderationService.isSubjectTakendown( - subject, - ) + const status = await moderationService.getStatus(subject) - if (isSubjectTakendown && isTakedownEvent) { + if (status?.takendown && isTakedownEvent) { throw new InvalidRequestError(`Subject is already taken down`) } - if (!isSubjectTakendown && isReverseTakedownEvent) { + if (!status?.takendown && isReverseTakedownEvent) { throw new InvalidRequestError(`Subject is not taken down`) } + + if (status?.takendown && isReverseTakedownEvent && subject.isRecord()) { + // due to the way blob status is modeled, we should reverse takedown on all + // blobs for the record being restored, which aren't taken down on another record. + subject.blobCids = status.blobCids ?? [] + } } const moderationEvent = await db.transaction(async (dbTxn) => { diff --git a/packages/ozone/src/db/migrations/20240201T051104136Z-mod-event-blobs.ts b/packages/ozone/src/db/migrations/20240201T051104136Z-mod-event-blobs.ts new file mode 100644 index 00000000000..21b5e893b23 --- /dev/null +++ b/packages/ozone/src/db/migrations/20240201T051104136Z-mod-event-blobs.ts @@ -0,0 +1,15 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .alterTable('moderation_event') + .addColumn('subjectBlobCids', 'jsonb') + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema + .alterTable('moderation_event') + .dropColumn('subjectBlobCids') + .execute() +} diff --git a/packages/ozone/src/db/migrations/index.ts b/packages/ozone/src/db/migrations/index.ts index d00857bf575..7560c5bf482 100644 --- a/packages/ozone/src/db/migrations/index.ts +++ b/packages/ozone/src/db/migrations/index.ts @@ -4,3 +4,4 @@ export * as _20231219T205730722Z from './20231219T205730722Z-init' export * as _20240116T085607200Z from './20240116T085607200Z-communication-template' +export * as _20240201T051104136Z from './20240201T051104136Z-mod-event-blobs' diff --git a/packages/ozone/src/db/schema/moderation_event.ts b/packages/ozone/src/db/schema/moderation_event.ts index 0cf7d07c1e5..1692a3546fb 100644 --- a/packages/ozone/src/db/schema/moderation_event.ts +++ b/packages/ozone/src/db/schema/moderation_event.ts @@ -19,6 +19,7 @@ export interface ModerationEvent { subjectDid: string subjectUri: string | null subjectCid: string | null + subjectBlobCids: string[] | null createLabelVals: string | null negateLabelVals: string | null comment: string | null diff --git a/packages/ozone/src/db/types.ts b/packages/ozone/src/db/types.ts index c38271ee119..b7a2b9bb8ec 100644 --- a/packages/ozone/src/db/types.ts +++ b/packages/ozone/src/db/types.ts @@ -1,5 +1,5 @@ import { Pool as PgPool } from 'pg' -import { DynamicModule, RawBuilder, SelectQueryBuilder } from 'kysely' +import { DynamicModule, RawBuilder, SelectQueryBuilder, sql } from 'kysely' export type DbRef = RawBuilder | ReturnType @@ -13,3 +13,8 @@ export type PgOptions = { poolMaxUses?: number poolIdleTimeoutMs?: number } + +export const jsonb = (val: T) => { + if (val === null) return sql`null` + return sql`${JSON.stringify(val)}::jsonb` +} diff --git a/packages/ozone/src/mod-service/index.ts b/packages/ozone/src/mod-service/index.ts index 0ce130bf8d2..21c930a4f74 100644 --- a/packages/ozone/src/mod-service/index.ts +++ b/packages/ozone/src/mod-service/index.ts @@ -41,6 +41,7 @@ import { import { BlobPushEvent } from '../db/schema/blob_push_event' import { BackgroundQueue } from '../background' import { EventPusher } from '../daemon' +import { jsonb } from '../db/types' export type ModerationServiceCreator = (db: Database) => ModerationService @@ -223,6 +224,8 @@ export class ModerationService { meta.subjectLine = event.subjectLine } + const subjectInfo = subject.info() + const modEvent = await this.db.db .insertInto('moderation_event') .values({ @@ -241,7 +244,11 @@ export class ModerationService { event.durationInHours ? addHoursToDate(event.durationInHours, createdAt).toISOString() : undefined, - ...subject.info(), + subjectType: subjectInfo.subjectType, + subjectDid: subjectInfo.subjectDid, + subjectUri: subjectInfo.subjectUri, + subjectCid: subjectInfo.subjectCid, + subjectBlobCids: jsonb(subjectInfo.subjectBlobCids), }) .returningAll() .executeTakeFirstOrThrow() @@ -713,15 +720,16 @@ export class ModerationService { } } - async isSubjectTakendown(subject: ModSubject): Promise { - const builder = this.db.db + async getStatus( + subject: ModSubject, + ): Promise { + const result = await this.db.db .selectFrom('moderation_subject_status') .where('did', '=', subject.did) - .where('recordPath', '=', subject.recordPath || '') - - const result = await builder.select('takendown').executeTakeFirst() - - return !!result?.takendown + .where('recordPath', '=', subject.recordPath ?? '') + .selectAll() + .executeTakeFirst() + return result ?? null } async formatAndCreateLabels( diff --git a/packages/ozone/src/mod-service/status.ts b/packages/ozone/src/mod-service/status.ts index 598ebe20712..359228a313d 100644 --- a/packages/ozone/src/mod-service/status.ts +++ b/packages/ozone/src/mod-service/status.ts @@ -12,6 +12,7 @@ import { ModerationEventRow, ModerationSubjectStatusRow } from './types' import { HOUR } from '@atproto/common' import { sql } from 'kysely' import { REASONAPPEAL } from '../lexicon/types/com/atproto/moderation/defs' +import { jsonb } from '../db/types' const getSubjectStatusForModerationEvent = ({ action, @@ -191,9 +192,9 @@ export const adjustModerationSubjectStatus = async ( } if (blobCids?.length) { - const newBlobCids = sql`${JSON.stringify( + const newBlobCids = jsonb( blobCids, - )}` as unknown as ModerationSubjectStatusRow['blobCids'] + ) as unknown as ModerationSubjectStatusRow['blobCids'] newStatus.blobCids = newBlobCids subjectStatus.blobCids = newBlobCids } diff --git a/packages/ozone/src/mod-service/subject.ts b/packages/ozone/src/mod-service/subject.ts index 2ea41eed42e..82c8f15f1d5 100644 --- a/packages/ozone/src/mod-service/subject.ts +++ b/packages/ozone/src/mod-service/subject.ts @@ -37,7 +37,11 @@ export const subjectFromEventRow = (row: ModerationEventRow): ModSubject => { row.subjectUri && row.subjectCid ) { - return new RecordSubject(row.subjectUri, row.subjectCid) + return new RecordSubject( + row.subjectUri, + row.subjectCid, + row.subjectBlobCids ?? [], + ) } else { return new RepoSubject(row.subjectDid) } @@ -50,7 +54,7 @@ export const subjectFromStatusRow = ( // Not too intuitive but the recordpath is basically / // which is what the last 2 params of .make() arguments are const uri = AtUri.make(row.did, ...row.recordPath.split('/')).toString() - return new RecordSubject(uri.toString(), row.recordCid) + return new RecordSubject(uri.toString(), row.recordCid, row.blobCids ?? []) } else { return new RepoSubject(row.did) } @@ -61,6 +65,7 @@ type SubjectInfo = { subjectDid: string subjectUri: string | null subjectCid: string | null + subjectBlobCids: string[] | null } export interface ModSubject { @@ -89,6 +94,7 @@ export class RepoSubject implements ModSubject { subjectDid: this.did, subjectUri: null, subjectCid: null, + subjectBlobCids: null, } } lex(): RepoRef { @@ -124,6 +130,7 @@ export class RecordSubject implements ModSubject { subjectDid: this.did, subjectUri: this.uri, subjectCid: this.cid, + subjectBlobCids: this.blobCids ?? [], } } lex(): StrongRef { diff --git a/packages/ozone/src/mod-service/views.ts b/packages/ozone/src/mod-service/views.ts index 1ae32126b8f..4c15a00218d 100644 --- a/packages/ozone/src/mod-service/views.ts +++ b/packages/ozone/src/mod-service/views.ts @@ -88,7 +88,7 @@ export class ModerationViews { comment: event.comment ?? undefined, }, subject: subjectFromEventRow(event).lex(), - subjectBlobCids: [], + subjectBlobCids: event.subjectBlobCids ?? [], createdBy: event.createdBy, createdAt: event.createdAt, subjectHandle: event.subjectHandle ?? undefined, diff --git a/packages/ozone/tests/moderation-events.test.ts b/packages/ozone/tests/moderation-events.test.ts index 73149dc06d8..53cae38e8f2 100644 --- a/packages/ozone/tests/moderation-events.test.ts +++ b/packages/ozone/tests/moderation-events.test.ts @@ -1,5 +1,9 @@ +import assert from 'node:assert' import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' -import AtpAgent, { ComAtprotoAdminDefs } from '@atproto/api' +import AtpAgent, { + ComAtprotoAdminDefs, + ComAtprotoAdminEmitModerationEvent, +} from '@atproto/api' import { forSnapshot } from './_util' import { REASONMISLEADING, @@ -12,7 +16,9 @@ describe('moderation-events', () => { let pdsAgent: AtpAgent let sc: SeedClient - const emitModerationEvent = async (eventData) => { + const emitModerationEvent = async ( + eventData: ComAtprotoAdminEmitModerationEvent.InputSchema, + ) => { return pdsAgent.api.com.atproto.admin.emitModerationEvent(eventData, { encoding: 'application/json', headers: network.bsky.adminAuthHeaders('moderator'), @@ -206,15 +212,68 @@ describe('moderation-events', () => { describe('get event', () => { it('gets an event by specific id', async () => { const { data } = await pdsAgent.api.com.atproto.admin.getModerationEvent( - { - id: 1, + { id: 1 }, + { headers: network.bsky.adminAuthHeaders('moderator') }, + ) + expect(forSnapshot(data)).toMatchSnapshot() + }) + }) + + describe('blobs', () => { + it('are tracked on takedown event', async () => { + const post = sc.posts[sc.dids.carol][0] + assert(post.images.length > 1) + await emitModerationEvent({ + event: { + $type: 'com.atproto.admin.defs#modEventTakedown', }, - { - headers: network.bsky.adminAuthHeaders('moderator'), + subject: { + $type: 'com.atproto.repo.strongRef', + uri: post.ref.uriStr, + cid: post.ref.cidStr, }, - ) + subjectBlobCids: [post.images[0].image.ref.toString()], + createdBy: sc.dids.alice, + }) + const { data: result } = + await pdsAgent.api.com.atproto.admin.queryModerationEvents( + { subject: post.ref.uriStr }, + { headers: network.ozone.adminAuthHeaders('moderator') }, + ) + expect(result.events[0]).toMatchObject({ + createdBy: sc.dids.alice, + event: { + $type: 'com.atproto.admin.defs#modEventTakedown', + }, + subjectBlobCids: [post.images[0].image.ref.toString()], + }) + }) - expect(forSnapshot(data)).toMatchSnapshot() + it("are tracked on reverse-takedown event even if they aren't specified", async () => { + const post = sc.posts[sc.dids.carol][0] + await emitModerationEvent({ + event: { + $type: 'com.atproto.admin.defs#modEventReverseTakedown', + }, + subject: { + $type: 'com.atproto.repo.strongRef', + uri: post.ref.uriStr, + cid: post.ref.cidStr, + }, + createdBy: sc.dids.alice, + }) + const { data: result } = + await pdsAgent.api.com.atproto.admin.queryModerationEvents( + { subject: post.ref.uriStr }, + { headers: network.ozone.adminAuthHeaders('moderator') }, + ) + expect(result.events[0]).toMatchObject({ + createdBy: sc.dids.alice, + event: { + $type: 'com.atproto.admin.defs#modEventReverseTakedown', + }, + subjectBlobCids: [post.images[0].image.ref.toString()], + }) }) }) }) diff --git a/packages/ozone/tests/moderation-statuses.test.ts b/packages/ozone/tests/moderation-statuses.test.ts index 5f63dbfe9a4..b4a2ed110a3 100644 --- a/packages/ozone/tests/moderation-statuses.test.ts +++ b/packages/ozone/tests/moderation-statuses.test.ts @@ -1,3 +1,4 @@ +import assert from 'node:assert' import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' import AtpAgent, { ComAtprotoAdminDefs, @@ -141,4 +142,58 @@ describe('moderation-statuses', () => { expect(listReviewedFirst.length).toEqual(list.length) }) }) + + describe('blobs', () => { + it('are tracked on takendown subject', async () => { + const post = sc.posts[sc.dids.carol][0] + assert(post.images.length > 1) + await emitModerationEvent({ + event: { + $type: 'com.atproto.admin.defs#modEventTakedown', + }, + subject: { + $type: 'com.atproto.repo.strongRef', + uri: post.ref.uriStr, + cid: post.ref.cidStr, + }, + subjectBlobCids: [post.images[0].image.ref.toString()], + createdBy: sc.dids.alice, + }) + const { data: result } = + await pdsAgent.api.com.atproto.admin.queryModerationStatuses( + { subject: post.ref.uriStr }, + { headers: network.ozone.adminAuthHeaders('moderator') }, + ) + expect(result.subjectStatuses.length).toBe(1) + expect(result.subjectStatuses[0]).toMatchObject({ + takendown: true, + subjectBlobCids: [post.images[0].image.ref.toString()], + }) + }) + + it('are tracked on reverse-takendown subject based on previous status', async () => { + const post = sc.posts[sc.dids.carol][0] + await emitModerationEvent({ + event: { + $type: 'com.atproto.admin.defs#modEventReverseTakedown', + }, + subject: { + $type: 'com.atproto.repo.strongRef', + uri: post.ref.uriStr, + cid: post.ref.cidStr, + }, + createdBy: sc.dids.alice, + }) + const { data: result } = + await pdsAgent.api.com.atproto.admin.queryModerationStatuses( + { subject: post.ref.uriStr }, + { headers: network.ozone.adminAuthHeaders('moderator') }, + ) + expect(result.subjectStatuses.length).toBe(1) + expect(result.subjectStatuses[0]).toMatchObject({ + takendown: false, + subjectBlobCids: [post.images[0].image.ref.toString()], + }) + }) + }) }) From e4ec7af03608949fc3b00a845f547a77599b5ad0 Mon Sep 17 00:00:00 2001 From: Foysal Ahamed Date: Tue, 6 Feb 2024 01:10:44 +0100 Subject: [PATCH 002/103] :sparkles: Add date range and comment filter to queryModerationEvents endpoint (#2124) * :sparkles: Add date range and comment filter to queryModerationEvents endpoint * :sparkles: Add report type and label filters and tests * tidy tests * :sparkles: commentKeyword -> comment and make comment and hasComment independent * :sparkles: Fix multiple label filter query * :memo: Add changesets * codegen tidy * add changeset * remove unused changeset --------- Co-authored-by: Devin Ivy --- .changeset/sharp-ducks-pump.md | 5 + lexicons/com/atproto/admin/defs.json | 4 +- .../atproto/admin/queryModerationEvents.json | 34 +++++++ packages/api/src/client/lexicons.ts | 43 +++++++++ .../client/types/com/atproto/admin/defs.ts | 2 + .../atproto/admin/queryModerationEvents.ts | 13 +++ packages/bsky/src/lexicon/lexicons.ts | 43 +++++++++ .../lexicon/types/com/atproto/admin/defs.ts | 2 + .../atproto/admin/queryModerationEvents.ts | 13 +++ .../bsky/tests/auto-moderator/labeler.test.ts | 2 + .../src/api/admin/queryModerationEvents.ts | 14 +++ packages/ozone/src/api/moderation/util.ts | 1 + packages/ozone/src/lexicon/lexicons.ts | 43 +++++++++ .../lexicon/types/com/atproto/admin/defs.ts | 2 + .../atproto/admin/queryModerationEvents.ts | 13 +++ packages/ozone/src/mod-service/index.ts | 41 ++++++++ .../ozone/tests/moderation-events.test.ts | 94 +++++++++++++++++++ packages/pds/src/lexicon/lexicons.ts | 43 +++++++++ .../lexicon/types/com/atproto/admin/defs.ts | 2 + .../atproto/admin/queryModerationEvents.ts | 13 +++ 20 files changed, 426 insertions(+), 1 deletion(-) create mode 100644 .changeset/sharp-ducks-pump.md diff --git a/.changeset/sharp-ducks-pump.md b/.changeset/sharp-ducks-pump.md new file mode 100644 index 00000000000..fb99138eafa --- /dev/null +++ b/.changeset/sharp-ducks-pump.md @@ -0,0 +1,5 @@ +--- +'@atproto/api': patch +--- + +Allow filtering for comment, label, report type and date range on queryModerationEvents endpoint. diff --git a/lexicons/com/atproto/admin/defs.json b/lexicons/com/atproto/admin/defs.json index 5a65ae31562..133deaf9383 100644 --- a/lexicons/com/atproto/admin/defs.json +++ b/lexicons/com/atproto/admin/defs.json @@ -33,7 +33,8 @@ "#modEventAcknowledge", "#modEventEscalate", "#modEventMute", - "#modEventEmail" + "#modEventEmail", + "#modEventResolveAppeal" ] }, "subject": { @@ -70,6 +71,7 @@ "#modEventAcknowledge", "#modEventEscalate", "#modEventMute", + "#modEventEmail", "#modEventResolveAppeal" ] }, diff --git a/lexicons/com/atproto/admin/queryModerationEvents.json b/lexicons/com/atproto/admin/queryModerationEvents.json index 70af1bf8ae5..887921cfe20 100644 --- a/lexicons/com/atproto/admin/queryModerationEvents.json +++ b/lexicons/com/atproto/admin/queryModerationEvents.json @@ -23,6 +23,16 @@ "enum": ["asc", "desc"], "description": "Sort direction for the events. Defaults to descending order of created at timestamp." }, + "createdAfter": { + "type": "string", + "format": "datetime", + "description": "Retrieve events created after a given timestamp" + }, + "createdBefore": { + "type": "string", + "format": "datetime", + "description": "Retrieve events created before a given timestamp" + }, "subject": { "type": "string", "format": "uri" }, "includeAllUserRecords": { "type": "boolean", @@ -35,6 +45,30 @@ "maximum": 100, "default": 50 }, + "hasComment": { + "type": "boolean", + "description": "If true, only events with comments are returned" + }, + "comment": { + "type": "string", + "description": "If specified, only events with comments containing the keyword are returned" + }, + "addedLabels": { + "type": "array", + "items": { "type": "string" }, + "description": "If specified, only events where all of these labels were added are returned" + }, + "removedLabels": { + "type": "array", + "items": { "type": "string" }, + "description": "If specified, only events where all of these labels were removed are returned" + }, + "reportTypes": { + "type": "array", + "items": { + "type": "string" + } + }, "cursor": { "type": "string" } } }, diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index 08a70f8ca1d..515098a8f49 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -91,6 +91,7 @@ export const schemaDict = { 'lex:com.atproto.admin.defs#modEventEscalate', 'lex:com.atproto.admin.defs#modEventMute', 'lex:com.atproto.admin.defs#modEventEmail', + 'lex:com.atproto.admin.defs#modEventResolveAppeal', ], }, subject: { @@ -147,6 +148,7 @@ export const schemaDict = { 'lex:com.atproto.admin.defs#modEventAcknowledge', 'lex:com.atproto.admin.defs#modEventEscalate', 'lex:com.atproto.admin.defs#modEventMute', + 'lex:com.atproto.admin.defs#modEventEmail', 'lex:com.atproto.admin.defs#modEventResolveAppeal', ], }, @@ -1450,6 +1452,16 @@ export const schemaDict = { description: 'Sort direction for the events. Defaults to descending order of created at timestamp.', }, + createdAfter: { + type: 'string', + format: 'datetime', + description: 'Retrieve events created after a given timestamp', + }, + createdBefore: { + type: 'string', + format: 'datetime', + description: 'Retrieve events created before a given timestamp', + }, subject: { type: 'string', format: 'uri', @@ -1466,6 +1478,37 @@ export const schemaDict = { maximum: 100, default: 50, }, + hasComment: { + type: 'boolean', + description: 'If true, only events with comments are returned', + }, + comment: { + type: 'string', + description: + 'If specified, only events with comments containing the keyword are returned', + }, + addedLabels: { + type: 'array', + items: { + type: 'string', + }, + description: + 'If specified, only events where all of these labels were added are returned', + }, + removedLabels: { + type: 'array', + items: { + type: 'string', + }, + description: + 'If specified, only events where all of these labels were removed are returned', + }, + reportTypes: { + type: 'array', + items: { + type: 'string', + }, + }, cursor: { type: 'string', }, diff --git a/packages/api/src/client/types/com/atproto/admin/defs.ts b/packages/api/src/client/types/com/atproto/admin/defs.ts index da154f8a845..c0d8157e6ac 100644 --- a/packages/api/src/client/types/com/atproto/admin/defs.ts +++ b/packages/api/src/client/types/com/atproto/admin/defs.ts @@ -40,6 +40,7 @@ export interface ModEventView { | ModEventEscalate | ModEventMute | ModEventEmail + | ModEventResolveAppeal | { $type: string; [k: string]: unknown } subject: | RepoRef @@ -76,6 +77,7 @@ export interface ModEventViewDetail { | ModEventAcknowledge | ModEventEscalate | ModEventMute + | ModEventEmail | ModEventResolveAppeal | { $type: string; [k: string]: unknown } subject: diff --git a/packages/api/src/client/types/com/atproto/admin/queryModerationEvents.ts b/packages/api/src/client/types/com/atproto/admin/queryModerationEvents.ts index ed21c739bcb..1540babae51 100644 --- a/packages/api/src/client/types/com/atproto/admin/queryModerationEvents.ts +++ b/packages/api/src/client/types/com/atproto/admin/queryModerationEvents.ts @@ -14,10 +14,23 @@ export interface QueryParams { createdBy?: string /** Sort direction for the events. Defaults to descending order of created at timestamp. */ sortDirection?: 'asc' | 'desc' + /** Retrieve events created after a given timestamp */ + createdAfter?: string + /** Retrieve events created before a given timestamp */ + createdBefore?: string subject?: string /** If true, events on all record types (posts, lists, profile etc.) owned by the did are returned */ includeAllUserRecords?: boolean limit?: number + /** If true, only events with comments are returned */ + hasComment?: boolean + /** If specified, only events with comments containing the keyword are returned */ + comment?: string + /** If specified, only events where all of these labels were added are returned */ + addedLabels?: string[] + /** If specified, only events where all of these labels were removed are returned */ + removedLabels?: string[] + reportTypes?: string[] cursor?: string } diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index 08a70f8ca1d..515098a8f49 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -91,6 +91,7 @@ export const schemaDict = { 'lex:com.atproto.admin.defs#modEventEscalate', 'lex:com.atproto.admin.defs#modEventMute', 'lex:com.atproto.admin.defs#modEventEmail', + 'lex:com.atproto.admin.defs#modEventResolveAppeal', ], }, subject: { @@ -147,6 +148,7 @@ export const schemaDict = { 'lex:com.atproto.admin.defs#modEventAcknowledge', 'lex:com.atproto.admin.defs#modEventEscalate', 'lex:com.atproto.admin.defs#modEventMute', + 'lex:com.atproto.admin.defs#modEventEmail', 'lex:com.atproto.admin.defs#modEventResolveAppeal', ], }, @@ -1450,6 +1452,16 @@ export const schemaDict = { description: 'Sort direction for the events. Defaults to descending order of created at timestamp.', }, + createdAfter: { + type: 'string', + format: 'datetime', + description: 'Retrieve events created after a given timestamp', + }, + createdBefore: { + type: 'string', + format: 'datetime', + description: 'Retrieve events created before a given timestamp', + }, subject: { type: 'string', format: 'uri', @@ -1466,6 +1478,37 @@ export const schemaDict = { maximum: 100, default: 50, }, + hasComment: { + type: 'boolean', + description: 'If true, only events with comments are returned', + }, + comment: { + type: 'string', + description: + 'If specified, only events with comments containing the keyword are returned', + }, + addedLabels: { + type: 'array', + items: { + type: 'string', + }, + description: + 'If specified, only events where all of these labels were added are returned', + }, + removedLabels: { + type: 'array', + items: { + type: 'string', + }, + description: + 'If specified, only events where all of these labels were removed are returned', + }, + reportTypes: { + type: 'array', + items: { + type: 'string', + }, + }, cursor: { type: 'string', }, diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/defs.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/defs.ts index 41be2ad96e7..2fab0bc19a4 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/defs.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/defs.ts @@ -40,6 +40,7 @@ export interface ModEventView { | ModEventEscalate | ModEventMute | ModEventEmail + | ModEventResolveAppeal | { $type: string; [k: string]: unknown } subject: | RepoRef @@ -76,6 +77,7 @@ export interface ModEventViewDetail { | ModEventAcknowledge | ModEventEscalate | ModEventMute + | ModEventEmail | ModEventResolveAppeal | { $type: string; [k: string]: unknown } subject: diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts index f3c4f1fbb95..9972b688b71 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts @@ -15,10 +15,23 @@ export interface QueryParams { createdBy?: string /** Sort direction for the events. Defaults to descending order of created at timestamp. */ sortDirection: 'asc' | 'desc' + /** Retrieve events created after a given timestamp */ + createdAfter?: string + /** Retrieve events created before a given timestamp */ + createdBefore?: string subject?: string /** If true, events on all record types (posts, lists, profile etc.) owned by the did are returned */ includeAllUserRecords: boolean limit: number + /** If true, only events with comments are returned */ + hasComment?: boolean + /** If specified, only events with comments containing the keyword are returned */ + comment?: string + /** If specified, only events where all of these labels were added are returned */ + addedLabels?: string[] + /** If specified, only events where all of these labels were removed are returned */ + removedLabels?: string[] + reportTypes?: string[] cursor?: string } diff --git a/packages/bsky/tests/auto-moderator/labeler.test.ts b/packages/bsky/tests/auto-moderator/labeler.test.ts index b735ebb28b2..e0414a0d627 100644 --- a/packages/bsky/tests/auto-moderator/labeler.test.ts +++ b/packages/bsky/tests/auto-moderator/labeler.test.ts @@ -103,6 +103,8 @@ describe('labeler', () => { subject: uri.toString(), limit: 10, types: [], + addedLabels: [], + removedLabels: [], }) expect(events.length).toBe(1) expect(events[0]).toMatchObject({ diff --git a/packages/ozone/src/api/admin/queryModerationEvents.ts b/packages/ozone/src/api/admin/queryModerationEvents.ts index 4c0cbdd1500..b2ea4df7323 100644 --- a/packages/ozone/src/api/admin/queryModerationEvents.ts +++ b/packages/ozone/src/api/admin/queryModerationEvents.ts @@ -13,7 +13,14 @@ export default function (server: Server, ctx: AppContext) { sortDirection = 'desc', types, includeAllUserRecords = false, + hasComment, + comment, createdBy, + createdAfter, + createdBefore, + addedLabels = [], + removedLabels = [], + reportTypes, } = params const db = ctx.db const modService = ctx.modService(db) @@ -25,6 +32,13 @@ export default function (server: Server, ctx: AppContext) { cursor, sortDirection, includeAllUserRecords, + hasComment, + comment, + createdAfter, + createdBefore, + addedLabels, + removedLabels, + reportTypes, }) return { encoding: 'application/json', diff --git a/packages/ozone/src/api/moderation/util.ts b/packages/ozone/src/api/moderation/util.ts index 040007d5e79..f78829240d0 100644 --- a/packages/ozone/src/api/moderation/util.ts +++ b/packages/ozone/src/api/moderation/util.ts @@ -62,4 +62,5 @@ const eventTypes = new Set([ 'com.atproto.admin.defs#modEventUnmute', 'com.atproto.admin.defs#modEventReverseTakedown', 'com.atproto.admin.defs#modEventEmail', + 'com.atproto.admin.defs#modEventResolveAppeal', ]) diff --git a/packages/ozone/src/lexicon/lexicons.ts b/packages/ozone/src/lexicon/lexicons.ts index 08a70f8ca1d..515098a8f49 100644 --- a/packages/ozone/src/lexicon/lexicons.ts +++ b/packages/ozone/src/lexicon/lexicons.ts @@ -91,6 +91,7 @@ export const schemaDict = { 'lex:com.atproto.admin.defs#modEventEscalate', 'lex:com.atproto.admin.defs#modEventMute', 'lex:com.atproto.admin.defs#modEventEmail', + 'lex:com.atproto.admin.defs#modEventResolveAppeal', ], }, subject: { @@ -147,6 +148,7 @@ export const schemaDict = { 'lex:com.atproto.admin.defs#modEventAcknowledge', 'lex:com.atproto.admin.defs#modEventEscalate', 'lex:com.atproto.admin.defs#modEventMute', + 'lex:com.atproto.admin.defs#modEventEmail', 'lex:com.atproto.admin.defs#modEventResolveAppeal', ], }, @@ -1450,6 +1452,16 @@ export const schemaDict = { description: 'Sort direction for the events. Defaults to descending order of created at timestamp.', }, + createdAfter: { + type: 'string', + format: 'datetime', + description: 'Retrieve events created after a given timestamp', + }, + createdBefore: { + type: 'string', + format: 'datetime', + description: 'Retrieve events created before a given timestamp', + }, subject: { type: 'string', format: 'uri', @@ -1466,6 +1478,37 @@ export const schemaDict = { maximum: 100, default: 50, }, + hasComment: { + type: 'boolean', + description: 'If true, only events with comments are returned', + }, + comment: { + type: 'string', + description: + 'If specified, only events with comments containing the keyword are returned', + }, + addedLabels: { + type: 'array', + items: { + type: 'string', + }, + description: + 'If specified, only events where all of these labels were added are returned', + }, + removedLabels: { + type: 'array', + items: { + type: 'string', + }, + description: + 'If specified, only events where all of these labels were removed are returned', + }, + reportTypes: { + type: 'array', + items: { + type: 'string', + }, + }, cursor: { type: 'string', }, diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/defs.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/defs.ts index 41be2ad96e7..2fab0bc19a4 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/defs.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/defs.ts @@ -40,6 +40,7 @@ export interface ModEventView { | ModEventEscalate | ModEventMute | ModEventEmail + | ModEventResolveAppeal | { $type: string; [k: string]: unknown } subject: | RepoRef @@ -76,6 +77,7 @@ export interface ModEventViewDetail { | ModEventAcknowledge | ModEventEscalate | ModEventMute + | ModEventEmail | ModEventResolveAppeal | { $type: string; [k: string]: unknown } subject: diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts index f3c4f1fbb95..9972b688b71 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts @@ -15,10 +15,23 @@ export interface QueryParams { createdBy?: string /** Sort direction for the events. Defaults to descending order of created at timestamp. */ sortDirection: 'asc' | 'desc' + /** Retrieve events created after a given timestamp */ + createdAfter?: string + /** Retrieve events created before a given timestamp */ + createdBefore?: string subject?: string /** If true, events on all record types (posts, lists, profile etc.) owned by the did are returned */ includeAllUserRecords: boolean limit: number + /** If true, only events with comments are returned */ + hasComment?: boolean + /** If specified, only events with comments containing the keyword are returned */ + comment?: string + /** If specified, only events where all of these labels were added are returned */ + addedLabels?: string[] + /** If specified, only events where all of these labels were removed are returned */ + removedLabels?: string[] + reportTypes?: string[] cursor?: string } diff --git a/packages/ozone/src/mod-service/index.ts b/packages/ozone/src/mod-service/index.ts index 21c930a4f74..f617301742b 100644 --- a/packages/ozone/src/mod-service/index.ts +++ b/packages/ozone/src/mod-service/index.ts @@ -97,6 +97,13 @@ export class ModerationService { includeAllUserRecords: boolean types: ModerationEvent['action'][] sortDirection?: 'asc' | 'desc' + hasComment?: boolean + comment?: string + createdAfter?: string + createdBefore?: string + addedLabels: string[] + removedLabels: string[] + reportTypes?: string[] }): Promise<{ cursor?: string; events: ModerationEventRow[] }> { const { subject, @@ -106,6 +113,13 @@ export class ModerationService { includeAllUserRecords, sortDirection = 'desc', types, + hasComment, + comment, + createdAfter, + createdBefore, + addedLabels, + removedLabels, + reportTypes, } = opts let builder = this.db.db.selectFrom('moderation_event').selectAll() if (subject) { @@ -140,6 +154,33 @@ export class ModerationService { if (createdBy) { builder = builder.where('createdBy', '=', createdBy) } + if (createdAfter) { + builder = builder.where('createdAt', '>=', createdAfter) + } + if (createdBefore) { + builder = builder.where('createdAt', '<=', createdBefore) + } + if (comment) { + builder = builder.where('comment', 'ilike', `%${comment}%`) + } + if (hasComment) { + builder = builder.where('comment', 'is not', null) + } + + // If multiple labels are passed, then only retrieve events where all those labels exist + if (addedLabels.length) { + addedLabels.forEach((label) => { + builder = builder.where('createLabelVals', 'ilike', `%${label}%`) + }) + } + if (removedLabels.length) { + removedLabels.forEach((label) => { + builder = builder.where('negateLabelVals', 'ilike', `%${label}%`) + }) + } + if (reportTypes?.length) { + builder = builder.where(sql`meta->>'reportType'`, 'in', reportTypes) + } const { ref } = this.db.db.dynamic const keyset = new TimeIdKeyset( diff --git a/packages/ozone/tests/moderation-events.test.ts b/packages/ozone/tests/moderation-events.test.ts index 53cae38e8f2..4d7b880f88e 100644 --- a/packages/ozone/tests/moderation-events.test.ts +++ b/packages/ozone/tests/moderation-events.test.ts @@ -6,6 +6,7 @@ import AtpAgent, { } from '@atproto/api' import { forSnapshot } from './_util' import { + REASONAPPEAL, REASONMISLEADING, REASONSPAM, } from '../src/lexicon/types/com/atproto/moderation/defs' @@ -207,6 +208,99 @@ describe('moderation-events', () => { expect(reversedEvents.length).toEqual(allEvents.data.events.length) expect(reversedEvents[0].id).toEqual(defaultEvents[4].id) }) + + it('returns report events matching reportType filters', async () => { + const [spamEvents, misleadingEvents] = await Promise.all([ + queryModerationEvents({ + reportTypes: [REASONSPAM], + }), + queryModerationEvents({ + reportTypes: [REASONMISLEADING, REASONAPPEAL], + }), + ]) + + expect(misleadingEvents.data.events.length).toEqual(2) + expect(spamEvents.data.events.length).toEqual(6) + }) + + it('returns events matching keyword in comment', async () => { + const [eventsWithX, eventsWithTest, eventsWithComment] = + await Promise.all([ + queryModerationEvents({ + comment: 'X', + }), + queryModerationEvents({ + comment: 'test', + }), + queryModerationEvents({ + hasComment: true, + }), + ]) + + expect(eventsWithX.data.events.length).toEqual(10) + expect(eventsWithTest.data.events.length).toEqual(0) + expect(eventsWithComment.data.events.length).toEqual(12) + }) + + it('returns events matching filter params for columns', async () => { + const [negatedLabelEvent, createdLabelEvent] = await Promise.all([ + emitModerationEvent({ + event: { + $type: 'com.atproto.admin.defs#modEventLabel', + comment: 'X', + negateLabelVals: ['L1', 'L2'], + createLabelVals: [], + }, + // Report bob's account by alice and vice versa + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: sc.dids.alice, + }, + createdBy: sc.dids.bob, + }), + emitModerationEvent({ + event: { + $type: 'com.atproto.admin.defs#modEventLabel', + comment: 'X', + createLabelVals: ['L1', 'L2'], + negateLabelVals: [], + }, + // Report bob's account by alice and vice versa + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: sc.dids.bob, + }, + createdBy: sc.dids.alice, + }), + ]) + const [withTwoLabels, withoutTwoLabels, withOneLabel, withoutOneLabel] = + await Promise.all([ + queryModerationEvents({ + addedLabels: ['L1', 'L3'], + }), + queryModerationEvents({ + removedLabels: ['L1', 'L2'], + }), + queryModerationEvents({ + addedLabels: ['L1'], + }), + queryModerationEvents({ + removedLabels: ['L2'], + }), + ]) + + // Verify that when querying for events where 2 different labels were added + // events where all of the labels from the list was added are returned + expect(withTwoLabels.data.events.length).toEqual(0) + expect(negatedLabelEvent.data.id).toEqual( + withoutTwoLabels.data.events[0].id, + ) + + expect(createdLabelEvent.data.id).toEqual(withOneLabel.data.events[0].id) + expect(negatedLabelEvent.data.id).toEqual( + withoutOneLabel.data.events[0].id, + ) + }) }) describe('get event', () => { diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index 08a70f8ca1d..515098a8f49 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -91,6 +91,7 @@ export const schemaDict = { 'lex:com.atproto.admin.defs#modEventEscalate', 'lex:com.atproto.admin.defs#modEventMute', 'lex:com.atproto.admin.defs#modEventEmail', + 'lex:com.atproto.admin.defs#modEventResolveAppeal', ], }, subject: { @@ -147,6 +148,7 @@ export const schemaDict = { 'lex:com.atproto.admin.defs#modEventAcknowledge', 'lex:com.atproto.admin.defs#modEventEscalate', 'lex:com.atproto.admin.defs#modEventMute', + 'lex:com.atproto.admin.defs#modEventEmail', 'lex:com.atproto.admin.defs#modEventResolveAppeal', ], }, @@ -1450,6 +1452,16 @@ export const schemaDict = { description: 'Sort direction for the events. Defaults to descending order of created at timestamp.', }, + createdAfter: { + type: 'string', + format: 'datetime', + description: 'Retrieve events created after a given timestamp', + }, + createdBefore: { + type: 'string', + format: 'datetime', + description: 'Retrieve events created before a given timestamp', + }, subject: { type: 'string', format: 'uri', @@ -1466,6 +1478,37 @@ export const schemaDict = { maximum: 100, default: 50, }, + hasComment: { + type: 'boolean', + description: 'If true, only events with comments are returned', + }, + comment: { + type: 'string', + description: + 'If specified, only events with comments containing the keyword are returned', + }, + addedLabels: { + type: 'array', + items: { + type: 'string', + }, + description: + 'If specified, only events where all of these labels were added are returned', + }, + removedLabels: { + type: 'array', + items: { + type: 'string', + }, + description: + 'If specified, only events where all of these labels were removed are returned', + }, + reportTypes: { + type: 'array', + items: { + type: 'string', + }, + }, cursor: { type: 'string', }, diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/defs.ts b/packages/pds/src/lexicon/types/com/atproto/admin/defs.ts index 41be2ad96e7..2fab0bc19a4 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/defs.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/defs.ts @@ -40,6 +40,7 @@ export interface ModEventView { | ModEventEscalate | ModEventMute | ModEventEmail + | ModEventResolveAppeal | { $type: string; [k: string]: unknown } subject: | RepoRef @@ -76,6 +77,7 @@ export interface ModEventViewDetail { | ModEventAcknowledge | ModEventEscalate | ModEventMute + | ModEventEmail | ModEventResolveAppeal | { $type: string; [k: string]: unknown } subject: diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts b/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts index f3c4f1fbb95..9972b688b71 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts @@ -15,10 +15,23 @@ export interface QueryParams { createdBy?: string /** Sort direction for the events. Defaults to descending order of created at timestamp. */ sortDirection: 'asc' | 'desc' + /** Retrieve events created after a given timestamp */ + createdAfter?: string + /** Retrieve events created before a given timestamp */ + createdBefore?: string subject?: string /** If true, events on all record types (posts, lists, profile etc.) owned by the did are returned */ includeAllUserRecords: boolean limit: number + /** If true, only events with comments are returned */ + hasComment?: boolean + /** If specified, only events with comments containing the keyword are returned */ + comment?: string + /** If specified, only events where all of these labels were added are returned */ + addedLabels?: string[] + /** If specified, only events where all of these labels were removed are returned */ + removedLabels?: string[] + reportTypes?: string[] cursor?: string } From 77118a66a7a5703e495464186246fb29fa9fa9df Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 10:42:24 -0500 Subject: [PATCH 003/103] Version packages (#2140) Co-authored-by: github-actions[bot] --- .changeset/sharp-ducks-pump.md | 5 ----- packages/api/CHANGELOG.md | 6 ++++++ packages/api/package.json | 2 +- packages/bsky/CHANGELOG.md | 7 +++++++ packages/bsky/package.json | 2 +- packages/dev-env/CHANGELOG.md | 10 ++++++++++ packages/dev-env/package.json | 2 +- packages/ozone/CHANGELOG.md | 7 +++++++ packages/ozone/package.json | 2 +- packages/pds/CHANGELOG.md | 7 +++++++ packages/pds/package.json | 2 +- 11 files changed, 42 insertions(+), 10 deletions(-) delete mode 100644 .changeset/sharp-ducks-pump.md diff --git a/.changeset/sharp-ducks-pump.md b/.changeset/sharp-ducks-pump.md deleted file mode 100644 index fb99138eafa..00000000000 --- a/.changeset/sharp-ducks-pump.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@atproto/api': patch ---- - -Allow filtering for comment, label, report type and date range on queryModerationEvents endpoint. diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md index 997f86931fa..807d8b791e8 100644 --- a/packages/api/CHANGELOG.md +++ b/packages/api/CHANGELOG.md @@ -1,5 +1,11 @@ # @atproto/api +## 0.9.6 + +### Patch Changes + +- [#2124](https://github.com/bluesky-social/atproto/pull/2124) [`e4ec7af03`](https://github.com/bluesky-social/atproto/commit/e4ec7af03608949fc3b00a845f547a77599b5ad0) Thanks [@foysalit](https://github.com/foysalit)! - Allow filtering for comment, label, report type and date range on queryModerationEvents endpoint. + ## 0.9.5 ### Patch Changes diff --git a/packages/api/package.json b/packages/api/package.json index e00c9438d2f..55ee2f5b2d5 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/api", - "version": "0.9.5", + "version": "0.9.6", "license": "MIT", "description": "Client library for atproto and Bluesky", "keywords": [ diff --git a/packages/bsky/CHANGELOG.md b/packages/bsky/CHANGELOG.md index 8ce5294eb24..426ee79acf2 100644 --- a/packages/bsky/CHANGELOG.md +++ b/packages/bsky/CHANGELOG.md @@ -1,5 +1,12 @@ # @atproto/bsky +## 0.0.29 + +### Patch Changes + +- Updated dependencies [[`e4ec7af03`](https://github.com/bluesky-social/atproto/commit/e4ec7af03608949fc3b00a845f547a77599b5ad0)]: + - @atproto/api@0.9.6 + ## 0.0.28 ### Patch Changes diff --git a/packages/bsky/package.json b/packages/bsky/package.json index 7187e17f1bd..ac301188e00 100644 --- a/packages/bsky/package.json +++ b/packages/bsky/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/bsky", - "version": "0.0.28", + "version": "0.0.29", "license": "MIT", "description": "Reference implementation of app.bsky App View (Bluesky API)", "keywords": [ diff --git a/packages/dev-env/CHANGELOG.md b/packages/dev-env/CHANGELOG.md index 8d2dd8efae3..132a071c68b 100644 --- a/packages/dev-env/CHANGELOG.md +++ b/packages/dev-env/CHANGELOG.md @@ -1,5 +1,15 @@ # @atproto/dev-env +## 0.2.29 + +### Patch Changes + +- Updated dependencies [[`e4ec7af03`](https://github.com/bluesky-social/atproto/commit/e4ec7af03608949fc3b00a845f547a77599b5ad0)]: + - @atproto/api@0.9.6 + - @atproto/bsky@0.0.29 + - @atproto/ozone@0.0.8 + - @atproto/pds@0.3.17 + ## 0.2.28 ### Patch Changes diff --git a/packages/dev-env/package.json b/packages/dev-env/package.json index c291aaf61fc..b38df90c2b9 100644 --- a/packages/dev-env/package.json +++ b/packages/dev-env/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/dev-env", - "version": "0.2.28", + "version": "0.2.29", "license": "MIT", "description": "Local development environment helper for atproto development", "keywords": [ diff --git a/packages/ozone/CHANGELOG.md b/packages/ozone/CHANGELOG.md index 79ab4db32ab..0a9503ab56e 100644 --- a/packages/ozone/CHANGELOG.md +++ b/packages/ozone/CHANGELOG.md @@ -1,5 +1,12 @@ # @atproto/ozone +## 0.0.8 + +### Patch Changes + +- Updated dependencies [[`e4ec7af03`](https://github.com/bluesky-social/atproto/commit/e4ec7af03608949fc3b00a845f547a77599b5ad0)]: + - @atproto/api@0.9.6 + ## 0.0.7 ### Patch Changes diff --git a/packages/ozone/package.json b/packages/ozone/package.json index 41e3556e567..6656a740e5c 100644 --- a/packages/ozone/package.json +++ b/packages/ozone/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/ozone", - "version": "0.0.7", + "version": "0.0.8", "license": "MIT", "description": "Backend service for moderating the Bluesky network.", "keywords": [ diff --git a/packages/pds/CHANGELOG.md b/packages/pds/CHANGELOG.md index 877a308473d..492741aa228 100644 --- a/packages/pds/CHANGELOG.md +++ b/packages/pds/CHANGELOG.md @@ -1,5 +1,12 @@ # @atproto/pds +## 0.3.17 + +### Patch Changes + +- Updated dependencies [[`e4ec7af03`](https://github.com/bluesky-social/atproto/commit/e4ec7af03608949fc3b00a845f547a77599b5ad0)]: + - @atproto/api@0.9.6 + ## 0.3.16 ### Patch Changes diff --git a/packages/pds/package.json b/packages/pds/package.json index 77f26ed8ce7..1c5cc838628 100644 --- a/packages/pds/package.json +++ b/packages/pds/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/pds", - "version": "0.3.16", + "version": "0.3.17", "license": "MIT", "description": "Reference implementation of atproto Personal Data Server (PDS)", "keywords": [ From 758de8743d90fd10aa543bbea3d047dc6a1ac7eb Mon Sep 17 00:00:00 2001 From: Daniel Holmgren Date: Wed, 7 Feb 2024 19:40:43 -0600 Subject: [PATCH 004/103] PDS pipethrough (#2150) * initial impl * re-codegen * tweak pipethrough * wip * read after write pipethrough * integrate read after write * setup pds pipethrough logic * pipe through repo rev header * fix up tests * apply pipethrough to most pds routes * apply pipethrough to remaining pds routes * fix/tidy * getRecord * final details for passthrough in xrpc-server * build --------- Co-authored-by: Devin Ivy --- .../workflows/build-and-push-pds-ghcr.yaml | 1 + .../types/app/bsky/actor/getPreferences.ts | 4 +- .../types/app/bsky/actor/getProfile.ts | 4 +- .../types/app/bsky/actor/getProfiles.ts | 4 +- .../types/app/bsky/actor/getSuggestions.ts | 4 +- .../types/app/bsky/actor/putPreferences.ts | 2 +- .../types/app/bsky/actor/searchActors.ts | 4 +- .../app/bsky/actor/searchActorsTypeahead.ts | 4 +- .../app/bsky/feed/describeFeedGenerator.ts | 4 +- .../types/app/bsky/feed/getActorFeeds.ts | 4 +- .../types/app/bsky/feed/getActorLikes.ts | 4 +- .../types/app/bsky/feed/getAuthorFeed.ts | 4 +- .../lexicon/types/app/bsky/feed/getFeed.ts | 4 +- .../types/app/bsky/feed/getFeedGenerator.ts | 4 +- .../types/app/bsky/feed/getFeedGenerators.ts | 4 +- .../types/app/bsky/feed/getFeedSkeleton.ts | 4 +- .../lexicon/types/app/bsky/feed/getLikes.ts | 4 +- .../types/app/bsky/feed/getListFeed.ts | 4 +- .../types/app/bsky/feed/getPostThread.ts | 4 +- .../lexicon/types/app/bsky/feed/getPosts.ts | 4 +- .../types/app/bsky/feed/getRepostedBy.ts | 4 +- .../types/app/bsky/feed/getSuggestedFeeds.ts | 4 +- .../types/app/bsky/feed/getTimeline.ts | 4 +- .../types/app/bsky/feed/searchPosts.ts | 4 +- .../lexicon/types/app/bsky/graph/getBlocks.ts | 4 +- .../types/app/bsky/graph/getFollowers.ts | 4 +- .../types/app/bsky/graph/getFollows.ts | 4 +- .../lexicon/types/app/bsky/graph/getList.ts | 4 +- .../types/app/bsky/graph/getListBlocks.ts | 4 +- .../types/app/bsky/graph/getListMutes.ts | 4 +- .../lexicon/types/app/bsky/graph/getLists.ts | 4 +- .../lexicon/types/app/bsky/graph/getMutes.ts | 4 +- .../types/app/bsky/graph/getRelationships.ts | 4 +- .../bsky/graph/getSuggestedFollowsByActor.ts | 4 +- .../lexicon/types/app/bsky/graph/muteActor.ts | 2 +- .../types/app/bsky/graph/muteActorList.ts | 2 +- .../types/app/bsky/graph/unmuteActor.ts | 2 +- .../types/app/bsky/graph/unmuteActorList.ts | 2 +- .../app/bsky/notification/getUnreadCount.ts | 4 +- .../bsky/notification/listNotifications.ts | 4 +- .../app/bsky/notification/registerPush.ts | 2 +- .../types/app/bsky/notification/updateSeen.ts | 2 +- .../unspecced/getPopularFeedGenerators.ts | 4 +- .../bsky/unspecced/getTaggedSuggestions.ts | 4 +- .../bsky/unspecced/searchActorsSkeleton.ts | 4 +- .../app/bsky/unspecced/searchPostsSkeleton.ts | 4 +- .../admin/createCommunicationTemplate.ts | 4 +- .../types/com/atproto/admin/deleteAccount.ts | 2 +- .../admin/deleteCommunicationTemplate.ts | 2 +- .../atproto/admin/disableAccountInvites.ts | 2 +- .../com/atproto/admin/disableInviteCodes.ts | 2 +- .../com/atproto/admin/emitModerationEvent.ts | 4 +- .../com/atproto/admin/enableAccountInvites.ts | 2 +- .../types/com/atproto/admin/getAccountInfo.ts | 4 +- .../com/atproto/admin/getAccountInfos.ts | 4 +- .../types/com/atproto/admin/getInviteCodes.ts | 4 +- .../com/atproto/admin/getModerationEvent.ts | 4 +- .../types/com/atproto/admin/getRecord.ts | 4 +- .../types/com/atproto/admin/getRepo.ts | 4 +- .../com/atproto/admin/getSubjectStatus.ts | 4 +- .../admin/listCommunicationTemplates.ts | 4 +- .../atproto/admin/queryModerationEvents.ts | 4 +- .../atproto/admin/queryModerationStatuses.ts | 4 +- .../types/com/atproto/admin/searchRepos.ts | 4 +- .../types/com/atproto/admin/sendEmail.ts | 4 +- .../com/atproto/admin/updateAccountEmail.ts | 2 +- .../com/atproto/admin/updateAccountHandle.ts | 2 +- .../admin/updateCommunicationTemplate.ts | 4 +- .../com/atproto/admin/updateSubjectStatus.ts | 4 +- .../com/atproto/identity/resolveHandle.ts | 4 +- .../com/atproto/identity/updateHandle.ts | 2 +- .../types/com/atproto/label/queryLabels.ts | 4 +- .../com/atproto/moderation/createReport.ts | 4 +- .../types/com/atproto/repo/applyWrites.ts | 2 +- .../types/com/atproto/repo/createRecord.ts | 4 +- .../types/com/atproto/repo/deleteRecord.ts | 2 +- .../types/com/atproto/repo/describeRepo.ts | 4 +- .../types/com/atproto/repo/getRecord.ts | 4 +- .../types/com/atproto/repo/listRecords.ts | 4 +- .../types/com/atproto/repo/putRecord.ts | 4 +- .../types/com/atproto/repo/uploadBlob.ts | 4 +- .../types/com/atproto/server/confirmEmail.ts | 2 +- .../types/com/atproto/server/createAccount.ts | 4 +- .../com/atproto/server/createAppPassword.ts | 4 +- .../com/atproto/server/createInviteCode.ts | 4 +- .../com/atproto/server/createInviteCodes.ts | 4 +- .../types/com/atproto/server/createSession.ts | 4 +- .../types/com/atproto/server/deleteAccount.ts | 2 +- .../types/com/atproto/server/deleteSession.ts | 2 +- .../com/atproto/server/describeServer.ts | 4 +- .../atproto/server/getAccountInviteCodes.ts | 4 +- .../types/com/atproto/server/getSession.ts | 4 +- .../com/atproto/server/listAppPasswords.ts | 4 +- .../com/atproto/server/refreshSession.ts | 4 +- .../atproto/server/requestAccountDelete.ts | 2 +- .../server/requestEmailConfirmation.ts | 2 +- .../com/atproto/server/requestEmailUpdate.ts | 4 +- .../atproto/server/requestPasswordReset.ts | 2 +- .../com/atproto/server/reserveSigningKey.ts | 4 +- .../types/com/atproto/server/resetPassword.ts | 2 +- .../com/atproto/server/revokeAppPassword.ts | 2 +- .../types/com/atproto/server/updateEmail.ts | 2 +- .../lexicon/types/com/atproto/sync/getBlob.ts | 4 +- .../types/com/atproto/sync/getBlocks.ts | 4 +- .../types/com/atproto/sync/getCheckout.ts | 4 +- .../lexicon/types/com/atproto/sync/getHead.ts | 4 +- .../types/com/atproto/sync/getLatestCommit.ts | 4 +- .../types/com/atproto/sync/getRecord.ts | 4 +- .../lexicon/types/com/atproto/sync/getRepo.ts | 4 +- .../types/com/atproto/sync/listBlobs.ts | 4 +- .../types/com/atproto/sync/listRepos.ts | 4 +- .../types/com/atproto/sync/notifyOfUpdate.ts | 2 +- .../types/com/atproto/sync/requestCrawl.ts | 2 +- .../com/atproto/temp/checkSignupQueue.ts | 4 +- .../types/com/atproto/temp/fetchLabels.ts | 4 +- .../types/com/atproto/temp/importRepo.ts | 4 +- .../types/com/atproto/temp/pushBlob.ts | 2 +- .../atproto/temp/requestPhoneVerification.ts | 2 +- .../types/com/atproto/temp/transferAccount.ts | 4 +- packages/lex-cli/src/codegen/server.ts | 7 +- .../types/app/bsky/actor/getPreferences.ts | 4 +- .../types/app/bsky/actor/getProfile.ts | 4 +- .../types/app/bsky/actor/getProfiles.ts | 4 +- .../types/app/bsky/actor/getSuggestions.ts | 4 +- .../types/app/bsky/actor/putPreferences.ts | 2 +- .../types/app/bsky/actor/searchActors.ts | 4 +- .../app/bsky/actor/searchActorsTypeahead.ts | 4 +- .../app/bsky/feed/describeFeedGenerator.ts | 4 +- .../types/app/bsky/feed/getActorFeeds.ts | 4 +- .../types/app/bsky/feed/getActorLikes.ts | 4 +- .../types/app/bsky/feed/getAuthorFeed.ts | 4 +- .../lexicon/types/app/bsky/feed/getFeed.ts | 4 +- .../types/app/bsky/feed/getFeedGenerator.ts | 4 +- .../types/app/bsky/feed/getFeedGenerators.ts | 4 +- .../types/app/bsky/feed/getFeedSkeleton.ts | 4 +- .../lexicon/types/app/bsky/feed/getLikes.ts | 4 +- .../types/app/bsky/feed/getListFeed.ts | 4 +- .../types/app/bsky/feed/getPostThread.ts | 4 +- .../lexicon/types/app/bsky/feed/getPosts.ts | 4 +- .../types/app/bsky/feed/getRepostedBy.ts | 4 +- .../types/app/bsky/feed/getSuggestedFeeds.ts | 4 +- .../types/app/bsky/feed/getTimeline.ts | 4 +- .../types/app/bsky/feed/searchPosts.ts | 4 +- .../lexicon/types/app/bsky/graph/getBlocks.ts | 4 +- .../types/app/bsky/graph/getFollowers.ts | 4 +- .../types/app/bsky/graph/getFollows.ts | 4 +- .../lexicon/types/app/bsky/graph/getList.ts | 4 +- .../types/app/bsky/graph/getListBlocks.ts | 4 +- .../types/app/bsky/graph/getListMutes.ts | 4 +- .../lexicon/types/app/bsky/graph/getLists.ts | 4 +- .../lexicon/types/app/bsky/graph/getMutes.ts | 4 +- .../types/app/bsky/graph/getRelationships.ts | 4 +- .../bsky/graph/getSuggestedFollowsByActor.ts | 4 +- .../lexicon/types/app/bsky/graph/muteActor.ts | 2 +- .../types/app/bsky/graph/muteActorList.ts | 2 +- .../types/app/bsky/graph/unmuteActor.ts | 2 +- .../types/app/bsky/graph/unmuteActorList.ts | 2 +- .../app/bsky/notification/getUnreadCount.ts | 4 +- .../bsky/notification/listNotifications.ts | 4 +- .../app/bsky/notification/registerPush.ts | 2 +- .../types/app/bsky/notification/updateSeen.ts | 2 +- .../unspecced/getPopularFeedGenerators.ts | 4 +- .../bsky/unspecced/getTaggedSuggestions.ts | 4 +- .../bsky/unspecced/searchActorsSkeleton.ts | 4 +- .../app/bsky/unspecced/searchPostsSkeleton.ts | 4 +- .../admin/createCommunicationTemplate.ts | 4 +- .../types/com/atproto/admin/deleteAccount.ts | 2 +- .../admin/deleteCommunicationTemplate.ts | 2 +- .../atproto/admin/disableAccountInvites.ts | 2 +- .../com/atproto/admin/disableInviteCodes.ts | 2 +- .../com/atproto/admin/emitModerationEvent.ts | 4 +- .../com/atproto/admin/enableAccountInvites.ts | 2 +- .../types/com/atproto/admin/getAccountInfo.ts | 4 +- .../com/atproto/admin/getAccountInfos.ts | 4 +- .../types/com/atproto/admin/getInviteCodes.ts | 4 +- .../com/atproto/admin/getModerationEvent.ts | 4 +- .../types/com/atproto/admin/getRecord.ts | 4 +- .../types/com/atproto/admin/getRepo.ts | 4 +- .../com/atproto/admin/getSubjectStatus.ts | 4 +- .../admin/listCommunicationTemplates.ts | 4 +- .../atproto/admin/queryModerationEvents.ts | 4 +- .../atproto/admin/queryModerationStatuses.ts | 4 +- .../types/com/atproto/admin/searchRepos.ts | 4 +- .../types/com/atproto/admin/sendEmail.ts | 4 +- .../com/atproto/admin/updateAccountEmail.ts | 2 +- .../com/atproto/admin/updateAccountHandle.ts | 2 +- .../admin/updateCommunicationTemplate.ts | 4 +- .../com/atproto/admin/updateSubjectStatus.ts | 4 +- .../com/atproto/identity/resolveHandle.ts | 4 +- .../com/atproto/identity/updateHandle.ts | 2 +- .../types/com/atproto/label/queryLabels.ts | 4 +- .../com/atproto/moderation/createReport.ts | 4 +- .../types/com/atproto/repo/applyWrites.ts | 2 +- .../types/com/atproto/repo/createRecord.ts | 4 +- .../types/com/atproto/repo/deleteRecord.ts | 2 +- .../types/com/atproto/repo/describeRepo.ts | 4 +- .../types/com/atproto/repo/getRecord.ts | 4 +- .../types/com/atproto/repo/listRecords.ts | 4 +- .../types/com/atproto/repo/putRecord.ts | 4 +- .../types/com/atproto/repo/uploadBlob.ts | 4 +- .../types/com/atproto/server/confirmEmail.ts | 2 +- .../types/com/atproto/server/createAccount.ts | 4 +- .../com/atproto/server/createAppPassword.ts | 4 +- .../com/atproto/server/createInviteCode.ts | 4 +- .../com/atproto/server/createInviteCodes.ts | 4 +- .../types/com/atproto/server/createSession.ts | 4 +- .../types/com/atproto/server/deleteAccount.ts | 2 +- .../types/com/atproto/server/deleteSession.ts | 2 +- .../com/atproto/server/describeServer.ts | 4 +- .../atproto/server/getAccountInviteCodes.ts | 4 +- .../types/com/atproto/server/getSession.ts | 4 +- .../com/atproto/server/listAppPasswords.ts | 4 +- .../com/atproto/server/refreshSession.ts | 4 +- .../atproto/server/requestAccountDelete.ts | 2 +- .../server/requestEmailConfirmation.ts | 2 +- .../com/atproto/server/requestEmailUpdate.ts | 4 +- .../atproto/server/requestPasswordReset.ts | 2 +- .../com/atproto/server/reserveSigningKey.ts | 4 +- .../types/com/atproto/server/resetPassword.ts | 2 +- .../com/atproto/server/revokeAppPassword.ts | 2 +- .../types/com/atproto/server/updateEmail.ts | 2 +- .../lexicon/types/com/atproto/sync/getBlob.ts | 4 +- .../types/com/atproto/sync/getBlocks.ts | 4 +- .../types/com/atproto/sync/getCheckout.ts | 4 +- .../lexicon/types/com/atproto/sync/getHead.ts | 4 +- .../types/com/atproto/sync/getLatestCommit.ts | 4 +- .../types/com/atproto/sync/getRecord.ts | 4 +- .../lexicon/types/com/atproto/sync/getRepo.ts | 4 +- .../types/com/atproto/sync/listBlobs.ts | 4 +- .../types/com/atproto/sync/listRepos.ts | 4 +- .../types/com/atproto/sync/notifyOfUpdate.ts | 2 +- .../types/com/atproto/sync/requestCrawl.ts | 2 +- .../com/atproto/temp/checkSignupQueue.ts | 4 +- .../types/com/atproto/temp/fetchLabels.ts | 4 +- .../types/com/atproto/temp/importRepo.ts | 4 +- .../types/com/atproto/temp/pushBlob.ts | 2 +- .../atproto/temp/requestPhoneVerification.ts | 2 +- .../types/com/atproto/temp/transferAccount.ts | 4 +- .../pds/src/api/app/bsky/actor/getProfile.ts | 26 ++++-- .../pds/src/api/app/bsky/actor/getProfiles.ts | 24 +++-- .../src/api/app/bsky/actor/getSuggestions.ts | 9 +- .../src/api/app/bsky/actor/searchActors.ts | 9 +- .../app/bsky/actor/searchActorsTypeahead.ts | 16 ++-- .../src/api/app/bsky/feed/getActorFeeds.ts | 9 +- .../src/api/app/bsky/feed/getActorLikes.ts | 25 +++-- .../src/api/app/bsky/feed/getAuthorFeed.ts | 22 +++-- packages/pds/src/api/app/bsky/feed/getFeed.ts | 14 +-- .../src/api/app/bsky/feed/getFeedGenerator.ts | 9 +- .../api/app/bsky/feed/getFeedGenerators.ts | 9 +- .../pds/src/api/app/bsky/feed/getLikes.ts | 9 +- .../pds/src/api/app/bsky/feed/getListFeed.ts | 9 +- .../src/api/app/bsky/feed/getPostThread.ts | 35 +++---- .../pds/src/api/app/bsky/feed/getPosts.ts | 9 +- .../src/api/app/bsky/feed/getRepostedBy.ts | 9 +- .../api/app/bsky/feed/getSuggestedFeeds.ts | 9 +- .../pds/src/api/app/bsky/feed/getTimeline.ts | 15 ++- .../pds/src/api/app/bsky/feed/searchPosts.ts | 9 +- .../pds/src/api/app/bsky/graph/getBlocks.ts | 9 +- .../src/api/app/bsky/graph/getFollowers.ts | 9 +- .../pds/src/api/app/bsky/graph/getFollows.ts | 9 +- .../pds/src/api/app/bsky/graph/getList.ts | 9 +- .../src/api/app/bsky/graph/getListBlocks.ts | 9 +- .../src/api/app/bsky/graph/getListMutes.ts | 9 +- .../pds/src/api/app/bsky/graph/getLists.ts | 9 +- .../pds/src/api/app/bsky/graph/getMutes.ts | 9 +- .../bsky/graph/getSuggestedFollowsByActor.ts | 16 ++-- .../app/bsky/notification/getUnreadCount.ts | 16 ++-- .../bsky/notification/listNotifications.ts | 16 ++-- .../unspecced/getPopularFeedGenerators.ts | 16 ++-- .../bsky/unspecced/getTaggedSuggestions.ts | 16 ++-- .../pds/src/api/com/atproto/repo/getRecord.ts | 11 ++- .../types/app/bsky/actor/getPreferences.ts | 4 +- .../types/app/bsky/actor/getProfile.ts | 4 +- .../types/app/bsky/actor/getProfiles.ts | 4 +- .../types/app/bsky/actor/getSuggestions.ts | 4 +- .../types/app/bsky/actor/putPreferences.ts | 2 +- .../types/app/bsky/actor/searchActors.ts | 4 +- .../app/bsky/actor/searchActorsTypeahead.ts | 4 +- .../app/bsky/feed/describeFeedGenerator.ts | 4 +- .../types/app/bsky/feed/getActorFeeds.ts | 4 +- .../types/app/bsky/feed/getActorLikes.ts | 4 +- .../types/app/bsky/feed/getAuthorFeed.ts | 4 +- .../lexicon/types/app/bsky/feed/getFeed.ts | 4 +- .../types/app/bsky/feed/getFeedGenerator.ts | 4 +- .../types/app/bsky/feed/getFeedGenerators.ts | 4 +- .../types/app/bsky/feed/getFeedSkeleton.ts | 4 +- .../lexicon/types/app/bsky/feed/getLikes.ts | 4 +- .../types/app/bsky/feed/getListFeed.ts | 4 +- .../types/app/bsky/feed/getPostThread.ts | 4 +- .../lexicon/types/app/bsky/feed/getPosts.ts | 4 +- .../types/app/bsky/feed/getRepostedBy.ts | 4 +- .../types/app/bsky/feed/getSuggestedFeeds.ts | 4 +- .../types/app/bsky/feed/getTimeline.ts | 4 +- .../types/app/bsky/feed/searchPosts.ts | 4 +- .../lexicon/types/app/bsky/graph/getBlocks.ts | 4 +- .../types/app/bsky/graph/getFollowers.ts | 4 +- .../types/app/bsky/graph/getFollows.ts | 4 +- .../lexicon/types/app/bsky/graph/getList.ts | 4 +- .../types/app/bsky/graph/getListBlocks.ts | 4 +- .../types/app/bsky/graph/getListMutes.ts | 4 +- .../lexicon/types/app/bsky/graph/getLists.ts | 4 +- .../lexicon/types/app/bsky/graph/getMutes.ts | 4 +- .../types/app/bsky/graph/getRelationships.ts | 4 +- .../bsky/graph/getSuggestedFollowsByActor.ts | 4 +- .../lexicon/types/app/bsky/graph/muteActor.ts | 2 +- .../types/app/bsky/graph/muteActorList.ts | 2 +- .../types/app/bsky/graph/unmuteActor.ts | 2 +- .../types/app/bsky/graph/unmuteActorList.ts | 2 +- .../app/bsky/notification/getUnreadCount.ts | 4 +- .../bsky/notification/listNotifications.ts | 4 +- .../app/bsky/notification/registerPush.ts | 2 +- .../types/app/bsky/notification/updateSeen.ts | 2 +- .../unspecced/getPopularFeedGenerators.ts | 4 +- .../bsky/unspecced/getTaggedSuggestions.ts | 4 +- .../bsky/unspecced/searchActorsSkeleton.ts | 4 +- .../app/bsky/unspecced/searchPostsSkeleton.ts | 4 +- .../admin/createCommunicationTemplate.ts | 4 +- .../types/com/atproto/admin/deleteAccount.ts | 2 +- .../admin/deleteCommunicationTemplate.ts | 2 +- .../atproto/admin/disableAccountInvites.ts | 2 +- .../com/atproto/admin/disableInviteCodes.ts | 2 +- .../com/atproto/admin/emitModerationEvent.ts | 4 +- .../com/atproto/admin/enableAccountInvites.ts | 2 +- .../types/com/atproto/admin/getAccountInfo.ts | 4 +- .../com/atproto/admin/getAccountInfos.ts | 4 +- .../types/com/atproto/admin/getInviteCodes.ts | 4 +- .../com/atproto/admin/getModerationEvent.ts | 4 +- .../types/com/atproto/admin/getRecord.ts | 4 +- .../types/com/atproto/admin/getRepo.ts | 4 +- .../com/atproto/admin/getSubjectStatus.ts | 4 +- .../admin/listCommunicationTemplates.ts | 4 +- .../atproto/admin/queryModerationEvents.ts | 4 +- .../atproto/admin/queryModerationStatuses.ts | 4 +- .../types/com/atproto/admin/searchRepos.ts | 4 +- .../types/com/atproto/admin/sendEmail.ts | 4 +- .../com/atproto/admin/updateAccountEmail.ts | 2 +- .../com/atproto/admin/updateAccountHandle.ts | 2 +- .../admin/updateCommunicationTemplate.ts | 4 +- .../com/atproto/admin/updateSubjectStatus.ts | 4 +- .../com/atproto/identity/resolveHandle.ts | 4 +- .../com/atproto/identity/updateHandle.ts | 2 +- .../types/com/atproto/label/queryLabels.ts | 4 +- .../com/atproto/moderation/createReport.ts | 4 +- .../types/com/atproto/repo/applyWrites.ts | 2 +- .../types/com/atproto/repo/createRecord.ts | 4 +- .../types/com/atproto/repo/deleteRecord.ts | 2 +- .../types/com/atproto/repo/describeRepo.ts | 4 +- .../types/com/atproto/repo/getRecord.ts | 4 +- .../types/com/atproto/repo/listRecords.ts | 4 +- .../types/com/atproto/repo/putRecord.ts | 4 +- .../types/com/atproto/repo/uploadBlob.ts | 4 +- .../types/com/atproto/server/confirmEmail.ts | 2 +- .../types/com/atproto/server/createAccount.ts | 4 +- .../com/atproto/server/createAppPassword.ts | 4 +- .../com/atproto/server/createInviteCode.ts | 4 +- .../com/atproto/server/createInviteCodes.ts | 4 +- .../types/com/atproto/server/createSession.ts | 4 +- .../types/com/atproto/server/deleteAccount.ts | 2 +- .../types/com/atproto/server/deleteSession.ts | 2 +- .../com/atproto/server/describeServer.ts | 4 +- .../atproto/server/getAccountInviteCodes.ts | 4 +- .../types/com/atproto/server/getSession.ts | 4 +- .../com/atproto/server/listAppPasswords.ts | 4 +- .../com/atproto/server/refreshSession.ts | 4 +- .../atproto/server/requestAccountDelete.ts | 2 +- .../server/requestEmailConfirmation.ts | 2 +- .../com/atproto/server/requestEmailUpdate.ts | 4 +- .../atproto/server/requestPasswordReset.ts | 2 +- .../com/atproto/server/reserveSigningKey.ts | 4 +- .../types/com/atproto/server/resetPassword.ts | 2 +- .../com/atproto/server/revokeAppPassword.ts | 2 +- .../types/com/atproto/server/updateEmail.ts | 2 +- .../lexicon/types/com/atproto/sync/getBlob.ts | 4 +- .../types/com/atproto/sync/getBlocks.ts | 4 +- .../types/com/atproto/sync/getCheckout.ts | 4 +- .../lexicon/types/com/atproto/sync/getHead.ts | 4 +- .../types/com/atproto/sync/getLatestCommit.ts | 4 +- .../types/com/atproto/sync/getRecord.ts | 4 +- .../lexicon/types/com/atproto/sync/getRepo.ts | 4 +- .../types/com/atproto/sync/listBlobs.ts | 4 +- .../types/com/atproto/sync/listRepos.ts | 4 +- .../types/com/atproto/sync/notifyOfUpdate.ts | 2 +- .../types/com/atproto/sync/requestCrawl.ts | 2 +- .../com/atproto/temp/checkSignupQueue.ts | 4 +- .../types/com/atproto/temp/fetchLabels.ts | 4 +- .../types/com/atproto/temp/importRepo.ts | 4 +- .../types/com/atproto/temp/pushBlob.ts | 2 +- .../atproto/temp/requestPhoneVerification.ts | 2 +- .../types/com/atproto/temp/transferAccount.ts | 4 +- packages/pds/src/pipethrough.ts | 93 +++++++++++++++++++ packages/pds/src/read-after-write/types.ts | 1 + packages/pds/src/read-after-write/util.ts | 67 +++++++------ packages/pds/src/read-after-write/viewer.ts | 93 ++++++++++--------- packages/xrpc-server/src/server.ts | 36 +++++++ packages/xrpc-server/src/types.ts | 9 +- 395 files changed, 1070 insertions(+), 912 deletions(-) create mode 100644 packages/pds/src/pipethrough.ts diff --git a/.github/workflows/build-and-push-pds-ghcr.yaml b/.github/workflows/build-and-push-pds-ghcr.yaml index b11230ab531..013388bd1f4 100644 --- a/.github/workflows/build-and-push-pds-ghcr.yaml +++ b/.github/workflows/build-and-push-pds-ghcr.yaml @@ -3,6 +3,7 @@ on: push: branches: - main + - pds-pipethrough env: REGISTRY: ghcr.io USERNAME: ${{ github.actor }} diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/getPreferences.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/getPreferences.ts index 88d78a57cba..305e80484be 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/getPreferences.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/getPreferences.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams {} @@ -31,7 +31,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/getProfile.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/getProfile.ts index 802afda5361..be58c73a233 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/getProfile.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/getProfile.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams { @@ -28,7 +28,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/getProfiles.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/getProfiles.ts index 2549b264e33..16438505654 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/getProfiles.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/getProfiles.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams { @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/getSuggestions.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/getSuggestions.ts index a6d4d6102af..33b89a18bfa 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/getSuggestions.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/getSuggestions.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/putPreferences.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/putPreferences.ts index 1e5ee2d834e..670e752fea3 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/putPreferences.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/putPreferences.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/searchActors.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/searchActors.ts index f072b8a4d04..dcda0c41854 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/searchActors.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/searchActors.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams { @@ -39,7 +39,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts index 0cf56753db2..0198b23d790 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts index d329bf20a5a..5bf8699a3ca 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -32,7 +32,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getActorFeeds.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getActorFeeds.ts index 3e930cbe201..0b8afff4ec8 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getActorFeeds.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getActorFeeds.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -36,7 +36,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getActorLikes.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getActorLikes.ts index df2f291e1a7..da315ae33c7 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getActorLikes.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getActorLikes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { error?: 'BlockedActor' | 'BlockedByActor' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts index 25f51f6fe5f..8f8e038c71f 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -43,7 +43,7 @@ export interface HandlerError { error?: 'BlockedActor' | 'BlockedByActor' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeed.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeed.ts index e72b1010aea..e03913a6fb3 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeed.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeed.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { error?: 'UnknownFeed' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts index fab3b30c316..b65a5151d46 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts index d7e082f2362..21963a91e2e 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts index 1c8f349b42b..ab1911ecb87 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { error?: 'UnknownFeed' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getLikes.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getLikes.ts index d581f5bfa9c..c7e8c860bd6 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getLikes.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getLikes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -39,7 +39,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getListFeed.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getListFeed.ts index e24c3f8ed22..ab157788f72 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getListFeed.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getListFeed.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { error?: 'UnknownList' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getPostThread.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getPostThread.ts index 61de94b729d..9e81da0eff6 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getPostThread.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getPostThread.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -40,7 +40,7 @@ export interface HandlerError { error?: 'NotFound' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getPosts.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getPosts.ts index 4282f5d349f..439e2132201 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getPosts.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getPosts.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getRepostedBy.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getRepostedBy.ts index 0b9c1a6f68b..6dea2b753c7 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getRepostedBy.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getRepostedBy.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -39,7 +39,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getSuggestedFeeds.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getSuggestedFeeds.ts index 9b271335466..d1ec590f33d 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getSuggestedFeeds.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getSuggestedFeeds.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getTimeline.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getTimeline.ts index 832caf5c6f7..f37def5808e 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getTimeline.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getTimeline.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -36,7 +36,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/searchPosts.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/searchPosts.ts index 36ac7cbb67d..9dae079c226 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/searchPosts.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/searchPosts.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -41,7 +41,7 @@ export interface HandlerError { error?: 'BadQueryString' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getBlocks.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getBlocks.ts index d380a14880a..1fc9cd8ce37 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getBlocks.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getBlocks.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getFollowers.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getFollowers.ts index b337be52c1b..f5645eaef29 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getFollowers.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getFollowers.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getFollows.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getFollows.ts index 71e9ca0270c..b9bd249da45 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getFollows.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getFollows.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getList.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getList.ts index fc45dd20985..6b30cd7faa9 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getList.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getList.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getListBlocks.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getListBlocks.ts index 04cca70b44d..7399a14fadc 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getListBlocks.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getListBlocks.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getListMutes.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getListMutes.ts index 04cca70b44d..7399a14fadc 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getListMutes.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getListMutes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getLists.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getLists.ts index 8acf9362c00..6bcb3134a47 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getLists.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getLists.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { @@ -36,7 +36,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getMutes.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getMutes.ts index 0034095b975..f450393522d 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getMutes.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getMutes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getRelationships.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getRelationships.ts index 32a27434782..0125414ccd4 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getRelationships.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getRelationships.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { @@ -40,7 +40,7 @@ export interface HandlerError { error?: 'ActorNotFound' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts index a2245846fd2..8f310334d0a 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/muteActor.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/muteActor.ts index 52d1b864989..baa9844046a 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/muteActor.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/muteActor.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/muteActorList.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/muteActorList.ts index bf803f388af..6a68f680a1c 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/muteActorList.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/muteActorList.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/unmuteActor.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/unmuteActor.ts index 52d1b864989..baa9844046a 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/unmuteActor.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/unmuteActor.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/unmuteActorList.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/unmuteActorList.ts index bf803f388af..6a68f680a1c 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/unmuteActorList.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/unmuteActorList.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/app/bsky/notification/getUnreadCount.ts b/packages/bsky/src/lexicon/types/app/bsky/notification/getUnreadCount.ts index 6cf3c84beb5..eae30df7c1b 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/notification/getUnreadCount.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/notification/getUnreadCount.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { seenAt?: string @@ -32,7 +32,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/notification/listNotifications.ts b/packages/bsky/src/lexicon/types/app/bsky/notification/listNotifications.ts index b50d6e8282e..d494494e569 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/notification/listNotifications.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/notification/listNotifications.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' @@ -38,7 +38,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/notification/registerPush.ts b/packages/bsky/src/lexicon/types/app/bsky/notification/registerPush.ts index 9923aeb058e..cce8f95839d 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/notification/registerPush.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/notification/registerPush.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/app/bsky/notification/updateSeen.ts b/packages/bsky/src/lexicon/types/app/bsky/notification/updateSeen.ts index 136191edc40..93db017a152 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/notification/updateSeen.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/notification/updateSeen.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts b/packages/bsky/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts index 97937e926c2..02f19f3cc6a 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from '../feed/defs' export interface QueryParams { @@ -36,7 +36,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/unspecced/getTaggedSuggestions.ts b/packages/bsky/src/lexicon/types/app/bsky/unspecced/getTaggedSuggestions.ts index e6319c54b4e..a03f442140d 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/unspecced/getTaggedSuggestions.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/unspecced/getTaggedSuggestions.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -30,7 +30,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/unspecced/searchActorsSkeleton.ts b/packages/bsky/src/lexicon/types/app/bsky/unspecced/searchActorsSkeleton.ts index 5c45b9fb622..4634407b890 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/unspecced/searchActorsSkeleton.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/unspecced/searchActorsSkeleton.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyUnspeccedDefs from './defs' export interface QueryParams { @@ -43,7 +43,7 @@ export interface HandlerError { error?: 'BadQueryString' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/app/bsky/unspecced/searchPostsSkeleton.ts b/packages/bsky/src/lexicon/types/app/bsky/unspecced/searchPostsSkeleton.ts index 15532087b82..860d4e8407c 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/unspecced/searchPostsSkeleton.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/unspecced/searchPostsSkeleton.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyUnspeccedDefs from './defs' export interface QueryParams { @@ -41,7 +41,7 @@ export interface HandlerError { error?: 'BadQueryString' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/createCommunicationTemplate.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/createCommunicationTemplate.ts index d42a8f2ef1d..b910b7987b4 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/createCommunicationTemplate.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/createCommunicationTemplate.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams {} @@ -41,7 +41,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/deleteAccount.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/deleteAccount.ts index 13e68eb5c7d..003c1b5ebcd 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/deleteAccount.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/deleteAccount.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/deleteCommunicationTemplate.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/deleteCommunicationTemplate.ts index 4bc6ec86fe4..c5ae5cd469f 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/deleteCommunicationTemplate.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/deleteCommunicationTemplate.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts index 62864923dfd..68c6503d95e 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts index 2b64371f1ed..2bf8de35583 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts index df44702b51c..3c0acabd0eb 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' import * as ComAtprotoRepoStrongRef from '../repo/strongRef' @@ -53,7 +53,7 @@ export interface HandlerError { error?: 'SubjectHasAction' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts index fb3aa8b8375..3f2836e7142 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/getAccountInfo.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/getAccountInfo.ts index 88a2b17a4b8..c7b840a153d 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/getAccountInfo.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/getAccountInfo.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -28,7 +28,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/getAccountInfos.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/getAccountInfos.ts index 46d917293a8..99ef44a99f5 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/getAccountInfos.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/getAccountInfos.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/getInviteCodes.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/getInviteCodes.ts index 1eb099aae66..d68b97d775a 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/getInviteCodes.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/getInviteCodes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoServerDefs from '../server/defs' export interface QueryParams { @@ -36,7 +36,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationEvent.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationEvent.ts index 7de567a73db..99c8bbe20ef 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationEvent.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/getModerationEvent.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -28,7 +28,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/getRecord.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/getRecord.ts index 48222d9d819..557945e2fbd 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/getRecord.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/getRecord.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -30,7 +30,7 @@ export interface HandlerError { error?: 'RecordNotFound' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/getRepo.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/getRepo.ts index 19911baa90a..ede9fcf3ce8 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/getRepo.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/getRepo.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -29,7 +29,7 @@ export interface HandlerError { error?: 'RepoNotFound' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/getSubjectStatus.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/getSubjectStatus.ts index 7315e51e8c2..d5976db70b1 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/getSubjectStatus.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/getSubjectStatus.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' import * as ComAtprotoRepoStrongRef from '../repo/strongRef' @@ -41,7 +41,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/listCommunicationTemplates.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/listCommunicationTemplates.ts index cb479533d39..843c228e6f9 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/listCommunicationTemplates.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/listCommunicationTemplates.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams {} @@ -31,7 +31,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts index 9972b688b71..df07cfaf2da 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -56,7 +56,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts index 6e1aea1f679..0cc21c25352 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -59,7 +59,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/searchRepos.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/searchRepos.ts index 1e7e1a36bb6..d1529956c17 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/searchRepos.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/searchRepos.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -38,7 +38,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/sendEmail.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/sendEmail.ts index f94cfb3a083..836fba39f79 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/sendEmail.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/sendEmail.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -41,7 +41,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts index 9e6140256ef..ebabffbccdb 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts index c378f421926..d6dc4a2dc25 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/updateCommunicationTemplate.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/updateCommunicationTemplate.ts index 5dc5cecda4a..73e079cfe58 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/updateCommunicationTemplate.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/updateCommunicationTemplate.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams {} @@ -44,7 +44,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/updateSubjectStatus.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/updateSubjectStatus.ts index 559ee948380..94df3041cf3 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/updateSubjectStatus.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/updateSubjectStatus.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' import * as ComAtprotoRepoStrongRef from '../repo/strongRef' @@ -48,7 +48,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/identity/resolveHandle.ts b/packages/bsky/src/lexicon/types/com/atproto/identity/resolveHandle.ts index ef90e99bb30..05019df6166 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/identity/resolveHandle.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/identity/resolveHandle.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The handle to resolve. */ @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/identity/updateHandle.ts b/packages/bsky/src/lexicon/types/com/atproto/identity/updateHandle.ts index 1f639c344e9..6782a68ed54 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/identity/updateHandle.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/identity/updateHandle.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/label/queryLabels.ts b/packages/bsky/src/lexicon/types/com/atproto/label/queryLabels.ts index 1d7f8a4def5..0c9d55a6961 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/label/queryLabels.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/label/queryLabels.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoLabelDefs from './defs' export interface QueryParams { @@ -39,7 +39,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/moderation/createReport.ts b/packages/bsky/src/lexicon/types/com/atproto/moderation/createReport.ts index 96aaf4a9c29..c1335eb3d1f 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/moderation/createReport.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/moderation/createReport.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoModerationDefs from './defs' import * as ComAtprotoAdminDefs from '../admin/defs' import * as ComAtprotoRepoStrongRef from '../repo/strongRef' @@ -52,7 +52,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/applyWrites.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/applyWrites.ts index 61d1e7c28e4..7bdb6dc2ed1 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/applyWrites.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/applyWrites.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/createRecord.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/createRecord.ts index df8c5d9e600..666b91c828d 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/createRecord.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/createRecord.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -49,7 +49,7 @@ export interface HandlerError { error?: 'InvalidSwap' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/deleteRecord.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/deleteRecord.ts index f45118a3769..65ee32d213d 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/deleteRecord.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/deleteRecord.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/describeRepo.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/describeRepo.ts index 7b8a2b995eb..38b9c01ef1c 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/describeRepo.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/describeRepo.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The handle or DID of the repo. */ @@ -37,7 +37,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/getRecord.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/getRecord.ts index 35c9b4b7166..345dde29a53 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/getRecord.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/getRecord.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The handle or DID of the repo. */ @@ -41,7 +41,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/listRecords.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/listRecords.ts index a6cf6abd1f3..f46f6eb0f7f 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/listRecords.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/listRecords.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The handle or DID of the repo. */ @@ -45,7 +45,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/putRecord.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/putRecord.ts index f10f773c1c4..de93e2e9cf7 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/putRecord.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/putRecord.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -51,7 +51,7 @@ export interface HandlerError { error?: 'InvalidSwap' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/uploadBlob.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/uploadBlob.ts index ad6002df925..febbbff9d16 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/uploadBlob.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/uploadBlob.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -34,7 +34,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/confirmEmail.ts b/packages/bsky/src/lexicon/types/com/atproto/server/confirmEmail.ts index ffaeeb8fe75..b667a04b996 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/confirmEmail.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/confirmEmail.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts b/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts index bbf2c009bf5..bceb61546cf 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -56,7 +56,7 @@ export interface HandlerError { | 'IncompatibleDidDoc' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/createAppPassword.ts b/packages/bsky/src/lexicon/types/com/atproto/server/createAppPassword.ts index 8e4a0a519e0..474846546fe 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/createAppPassword.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/createAppPassword.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -34,7 +34,7 @@ export interface HandlerError { error?: 'AccountTakedown' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/createInviteCode.ts b/packages/bsky/src/lexicon/types/com/atproto/server/createInviteCode.ts index acfac56ba76..9cfeacc7e28 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/createInviteCode.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/createInviteCode.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -37,7 +37,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/createInviteCodes.ts b/packages/bsky/src/lexicon/types/com/atproto/server/createInviteCodes.ts index 5887d77fada..eb6cd2bb1b1 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/createInviteCodes.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/createInviteCodes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -38,7 +38,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/createSession.ts b/packages/bsky/src/lexicon/types/com/atproto/server/createSession.ts index 2cd448703a6..3952959fe5e 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/createSession.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/createSession.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -45,7 +45,7 @@ export interface HandlerError { error?: 'AccountTakedown' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/deleteAccount.ts b/packages/bsky/src/lexicon/types/com/atproto/server/deleteAccount.ts index 37ddbba13e0..4fcec360a11 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/deleteAccount.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/deleteAccount.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/deleteSession.ts b/packages/bsky/src/lexicon/types/com/atproto/server/deleteSession.ts index e4244870425..82672f1d1c7 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/deleteSession.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/deleteSession.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/describeServer.ts b/packages/bsky/src/lexicon/types/com/atproto/server/describeServer.ts index bb574dba9ff..47be5b598b0 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/describeServer.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/describeServer.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts b/packages/bsky/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts index e387a5e38e4..2dc551a477c 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoServerDefs from './defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { error?: 'DuplicateCreate' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/getSession.ts b/packages/bsky/src/lexicon/types/com/atproto/server/getSession.ts index 4f95acf523d..5a8c40b947e 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/getSession.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/getSession.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -34,7 +34,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/listAppPasswords.ts b/packages/bsky/src/lexicon/types/com/atproto/server/listAppPasswords.ts index ebd74da9d39..241418d932d 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/listAppPasswords.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/listAppPasswords.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -31,7 +31,7 @@ export interface HandlerError { error?: 'AccountTakedown' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/refreshSession.ts b/packages/bsky/src/lexicon/types/com/atproto/server/refreshSession.ts index 35874f78a69..3adeb7fc20e 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/refreshSession.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/refreshSession.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -35,7 +35,7 @@ export interface HandlerError { error?: 'AccountTakedown' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/requestAccountDelete.ts b/packages/bsky/src/lexicon/types/com/atproto/server/requestAccountDelete.ts index e4244870425..82672f1d1c7 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/requestAccountDelete.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/requestAccountDelete.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/requestEmailConfirmation.ts b/packages/bsky/src/lexicon/types/com/atproto/server/requestEmailConfirmation.ts index e4244870425..82672f1d1c7 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/requestEmailConfirmation.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/requestEmailConfirmation.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/requestEmailUpdate.ts b/packages/bsky/src/lexicon/types/com/atproto/server/requestEmailUpdate.ts index 6876d44ca46..24dce3e12af 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/requestEmailUpdate.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/requestEmailUpdate.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -30,7 +30,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/requestPasswordReset.ts b/packages/bsky/src/lexicon/types/com/atproto/server/requestPasswordReset.ts index 47fb4bb62f3..d0f3f2ad769 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/requestPasswordReset.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/requestPasswordReset.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/reserveSigningKey.ts b/packages/bsky/src/lexicon/types/com/atproto/server/reserveSigningKey.ts index ad5a5a8758c..0de1220f4b5 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/reserveSigningKey.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/reserveSigningKey.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -38,7 +38,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/resetPassword.ts b/packages/bsky/src/lexicon/types/com/atproto/server/resetPassword.ts index 9e6ece3e4c4..38f63382cf0 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/resetPassword.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/resetPassword.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/revokeAppPassword.ts b/packages/bsky/src/lexicon/types/com/atproto/server/revokeAppPassword.ts index 4627f68eaa2..769ad6aa521 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/revokeAppPassword.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/revokeAppPassword.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/updateEmail.ts b/packages/bsky/src/lexicon/types/com/atproto/server/updateEmail.ts index c88bd3021b2..5473d7571e9 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/updateEmail.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/updateEmail.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/getBlob.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/getBlob.ts index 60750902472..b3980fca500 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/getBlob.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/getBlob.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -30,7 +30,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/getBlocks.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/getBlocks.ts index e73410efb41..f1b8ebe5db1 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/getBlocks.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/getBlocks.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -29,7 +29,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/getCheckout.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/getCheckout.ts index 63a657e56b9..51856b9088d 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/getCheckout.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/getCheckout.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -28,7 +28,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/getHead.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/getHead.ts index 586ae1a4189..adedd4cf211 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/getHead.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/getHead.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -34,7 +34,7 @@ export interface HandlerError { error?: 'HeadNotFound' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/getLatestCommit.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/getLatestCommit.ts index 9b91e878724..bbae68bbe76 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/getLatestCommit.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/getLatestCommit.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -35,7 +35,7 @@ export interface HandlerError { error?: 'RepoNotFound' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/getRecord.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/getRecord.ts index 297f0ac7794..e27878ff5e6 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/getRecord.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/getRecord.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -32,7 +32,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/getRepo.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/getRepo.ts index 495d31a1a22..e0ad53ded7c 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/getRepo.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/getRepo.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -30,7 +30,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/listBlobs.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/listBlobs.ts index b397bb3b3df..67a66577809 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/listBlobs.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/listBlobs.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -38,7 +38,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/listRepos.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/listRepos.ts index 783a8e314c2..12532860895 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/listRepos.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/listRepos.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { limit: number @@ -34,7 +34,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts index 3d310c1139a..f9498e6691d 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/requestCrawl.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/requestCrawl.ts index 87ef20d7297..2859e28fe69 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/requestCrawl.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/requestCrawl.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/temp/checkSignupQueue.ts b/packages/bsky/src/lexicon/types/com/atproto/temp/checkSignupQueue.ts index d2a431430a8..9486bce2b2b 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/temp/checkSignupQueue.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/temp/checkSignupQueue.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -32,7 +32,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/temp/fetchLabels.ts b/packages/bsky/src/lexicon/types/com/atproto/temp/fetchLabels.ts index 39341fd3a0e..0fbdeed1196 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/temp/fetchLabels.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/temp/fetchLabels.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoLabelDefs from '../label/defs' export interface QueryParams { @@ -34,7 +34,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/temp/importRepo.ts b/packages/bsky/src/lexicon/types/com/atproto/temp/importRepo.ts index d88361d9856..44bce41481c 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/temp/importRepo.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/temp/importRepo.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -32,7 +32,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/bsky/src/lexicon/types/com/atproto/temp/pushBlob.ts b/packages/bsky/src/lexicon/types/com/atproto/temp/pushBlob.ts index 97e890dbb14..d18a60b598f 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/temp/pushBlob.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/temp/pushBlob.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ diff --git a/packages/bsky/src/lexicon/types/com/atproto/temp/requestPhoneVerification.ts b/packages/bsky/src/lexicon/types/com/atproto/temp/requestPhoneVerification.ts index 5a295f701eb..c977500fc33 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/temp/requestPhoneVerification.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/temp/requestPhoneVerification.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/bsky/src/lexicon/types/com/atproto/temp/transferAccount.ts b/packages/bsky/src/lexicon/types/com/atproto/temp/transferAccount.ts index 86c1d750e07..565150caba2 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/temp/transferAccount.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/temp/transferAccount.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -49,7 +49,7 @@ export interface HandlerError { | 'IncompatibleDidDoc' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/lex-cli/src/codegen/server.ts b/packages/lex-cli/src/codegen/server.ts index 13e6ecd8e87..f220ed315c4 100644 --- a/packages/lex-cli/src/codegen/server.ts +++ b/packages/lex-cli/src/codegen/server.ts @@ -408,7 +408,7 @@ function genServerXrpcMethod( file.addImportDeclaration({ moduleSpecifier: '@atproto/xrpc-server', - namedImports: [{ name: 'HandlerAuth' }], + namedImports: [{ name: 'HandlerAuth' }, { name: 'HandlerPipeThrough' }], }) //= export interface HandlerInput {...} if (def.type === 'procedure' && def.input?.encoding) { @@ -452,6 +452,7 @@ function genServerXrpcMethod( name: 'HandlerSuccess', isExported: true, }) + if (def.output.encoding) { handlerSuccess.addProperty({ name: 'encoding', @@ -502,7 +503,9 @@ function genServerXrpcMethod( file.addTypeAlias({ isExported: true, name: 'HandlerOutput', - type: `HandlerError | ${hasHandlerSuccess ? 'HandlerSuccess' : 'void'}`, + type: `HandlerError | ${ + hasHandlerSuccess ? 'HandlerSuccess | HandlerPipeThrough' : 'void' + }`, }) file.addTypeAlias({ diff --git a/packages/ozone/src/lexicon/types/app/bsky/actor/getPreferences.ts b/packages/ozone/src/lexicon/types/app/bsky/actor/getPreferences.ts index 88d78a57cba..305e80484be 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/actor/getPreferences.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/actor/getPreferences.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams {} @@ -31,7 +31,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/actor/getProfile.ts b/packages/ozone/src/lexicon/types/app/bsky/actor/getProfile.ts index 802afda5361..be58c73a233 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/actor/getProfile.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/actor/getProfile.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams { @@ -28,7 +28,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/actor/getProfiles.ts b/packages/ozone/src/lexicon/types/app/bsky/actor/getProfiles.ts index 2549b264e33..16438505654 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/actor/getProfiles.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/actor/getProfiles.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams { @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/actor/getSuggestions.ts b/packages/ozone/src/lexicon/types/app/bsky/actor/getSuggestions.ts index a6d4d6102af..33b89a18bfa 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/actor/getSuggestions.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/actor/getSuggestions.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/actor/putPreferences.ts b/packages/ozone/src/lexicon/types/app/bsky/actor/putPreferences.ts index 1e5ee2d834e..670e752fea3 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/actor/putPreferences.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/actor/putPreferences.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/app/bsky/actor/searchActors.ts b/packages/ozone/src/lexicon/types/app/bsky/actor/searchActors.ts index f072b8a4d04..dcda0c41854 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/actor/searchActors.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/actor/searchActors.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams { @@ -39,7 +39,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts b/packages/ozone/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts index 0cf56753db2..0198b23d790 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts index d329bf20a5a..5bf8699a3ca 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -32,7 +32,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getActorFeeds.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getActorFeeds.ts index 3e930cbe201..0b8afff4ec8 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getActorFeeds.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getActorFeeds.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -36,7 +36,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getActorLikes.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getActorLikes.ts index df2f291e1a7..da315ae33c7 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getActorLikes.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getActorLikes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { error?: 'BlockedActor' | 'BlockedByActor' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts index 25f51f6fe5f..8f8e038c71f 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -43,7 +43,7 @@ export interface HandlerError { error?: 'BlockedActor' | 'BlockedByActor' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getFeed.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getFeed.ts index e72b1010aea..e03913a6fb3 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getFeed.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getFeed.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { error?: 'UnknownFeed' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts index fab3b30c316..b65a5151d46 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts index d7e082f2362..21963a91e2e 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts index 1c8f349b42b..ab1911ecb87 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { error?: 'UnknownFeed' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getLikes.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getLikes.ts index d581f5bfa9c..c7e8c860bd6 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getLikes.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getLikes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -39,7 +39,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getListFeed.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getListFeed.ts index e24c3f8ed22..ab157788f72 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getListFeed.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getListFeed.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { error?: 'UnknownList' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getPostThread.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getPostThread.ts index 61de94b729d..9e81da0eff6 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getPostThread.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getPostThread.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -40,7 +40,7 @@ export interface HandlerError { error?: 'NotFound' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getPosts.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getPosts.ts index 4282f5d349f..439e2132201 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getPosts.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getPosts.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getRepostedBy.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getRepostedBy.ts index 0b9c1a6f68b..6dea2b753c7 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getRepostedBy.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getRepostedBy.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -39,7 +39,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getSuggestedFeeds.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getSuggestedFeeds.ts index 9b271335466..d1ec590f33d 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getSuggestedFeeds.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getSuggestedFeeds.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getTimeline.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getTimeline.ts index 832caf5c6f7..f37def5808e 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getTimeline.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getTimeline.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -36,7 +36,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/searchPosts.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/searchPosts.ts index 36ac7cbb67d..9dae079c226 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/searchPosts.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/searchPosts.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -41,7 +41,7 @@ export interface HandlerError { error?: 'BadQueryString' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/getBlocks.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/getBlocks.ts index d380a14880a..1fc9cd8ce37 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/getBlocks.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/getBlocks.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/getFollowers.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/getFollowers.ts index b337be52c1b..f5645eaef29 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/getFollowers.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/getFollowers.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/getFollows.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/getFollows.ts index 71e9ca0270c..b9bd249da45 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/getFollows.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/getFollows.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/getList.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/getList.ts index fc45dd20985..6b30cd7faa9 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/getList.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/getList.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/getListBlocks.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/getListBlocks.ts index 04cca70b44d..7399a14fadc 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/getListBlocks.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/getListBlocks.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/getListMutes.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/getListMutes.ts index 04cca70b44d..7399a14fadc 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/getListMutes.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/getListMutes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/getLists.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/getLists.ts index 8acf9362c00..6bcb3134a47 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/getLists.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/getLists.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { @@ -36,7 +36,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/getMutes.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/getMutes.ts index 0034095b975..f450393522d 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/getMutes.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/getMutes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/getRelationships.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/getRelationships.ts index 32a27434782..0125414ccd4 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/getRelationships.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/getRelationships.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { @@ -40,7 +40,7 @@ export interface HandlerError { error?: 'ActorNotFound' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts index a2245846fd2..8f310334d0a 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/muteActor.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/muteActor.ts index 52d1b864989..baa9844046a 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/muteActor.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/muteActor.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/muteActorList.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/muteActorList.ts index bf803f388af..6a68f680a1c 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/muteActorList.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/muteActorList.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/unmuteActor.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/unmuteActor.ts index 52d1b864989..baa9844046a 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/unmuteActor.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/unmuteActor.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/unmuteActorList.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/unmuteActorList.ts index bf803f388af..6a68f680a1c 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/unmuteActorList.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/unmuteActorList.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/app/bsky/notification/getUnreadCount.ts b/packages/ozone/src/lexicon/types/app/bsky/notification/getUnreadCount.ts index 6cf3c84beb5..eae30df7c1b 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/notification/getUnreadCount.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/notification/getUnreadCount.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { seenAt?: string @@ -32,7 +32,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/notification/listNotifications.ts b/packages/ozone/src/lexicon/types/app/bsky/notification/listNotifications.ts index b50d6e8282e..d494494e569 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/notification/listNotifications.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/notification/listNotifications.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' @@ -38,7 +38,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/notification/registerPush.ts b/packages/ozone/src/lexicon/types/app/bsky/notification/registerPush.ts index 9923aeb058e..cce8f95839d 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/notification/registerPush.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/notification/registerPush.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/app/bsky/notification/updateSeen.ts b/packages/ozone/src/lexicon/types/app/bsky/notification/updateSeen.ts index 136191edc40..93db017a152 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/notification/updateSeen.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/notification/updateSeen.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts b/packages/ozone/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts index 97937e926c2..02f19f3cc6a 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from '../feed/defs' export interface QueryParams { @@ -36,7 +36,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/unspecced/getTaggedSuggestions.ts b/packages/ozone/src/lexicon/types/app/bsky/unspecced/getTaggedSuggestions.ts index e6319c54b4e..a03f442140d 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/unspecced/getTaggedSuggestions.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/unspecced/getTaggedSuggestions.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -30,7 +30,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/unspecced/searchActorsSkeleton.ts b/packages/ozone/src/lexicon/types/app/bsky/unspecced/searchActorsSkeleton.ts index 5c45b9fb622..4634407b890 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/unspecced/searchActorsSkeleton.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/unspecced/searchActorsSkeleton.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyUnspeccedDefs from './defs' export interface QueryParams { @@ -43,7 +43,7 @@ export interface HandlerError { error?: 'BadQueryString' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/app/bsky/unspecced/searchPostsSkeleton.ts b/packages/ozone/src/lexicon/types/app/bsky/unspecced/searchPostsSkeleton.ts index 15532087b82..860d4e8407c 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/unspecced/searchPostsSkeleton.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/unspecced/searchPostsSkeleton.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyUnspeccedDefs from './defs' export interface QueryParams { @@ -41,7 +41,7 @@ export interface HandlerError { error?: 'BadQueryString' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/createCommunicationTemplate.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/createCommunicationTemplate.ts index d42a8f2ef1d..b910b7987b4 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/createCommunicationTemplate.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/createCommunicationTemplate.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams {} @@ -41,7 +41,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/deleteAccount.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/deleteAccount.ts index 13e68eb5c7d..003c1b5ebcd 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/deleteAccount.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/deleteAccount.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/deleteCommunicationTemplate.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/deleteCommunicationTemplate.ts index 4bc6ec86fe4..c5ae5cd469f 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/deleteCommunicationTemplate.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/deleteCommunicationTemplate.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts index 62864923dfd..68c6503d95e 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts index 2b64371f1ed..2bf8de35583 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts index df44702b51c..3c0acabd0eb 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' import * as ComAtprotoRepoStrongRef from '../repo/strongRef' @@ -53,7 +53,7 @@ export interface HandlerError { error?: 'SubjectHasAction' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts index fb3aa8b8375..3f2836e7142 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/getAccountInfo.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/getAccountInfo.ts index 88a2b17a4b8..c7b840a153d 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/getAccountInfo.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/getAccountInfo.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -28,7 +28,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/getAccountInfos.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/getAccountInfos.ts index 46d917293a8..99ef44a99f5 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/getAccountInfos.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/getAccountInfos.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/getInviteCodes.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/getInviteCodes.ts index 1eb099aae66..d68b97d775a 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/getInviteCodes.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/getInviteCodes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoServerDefs from '../server/defs' export interface QueryParams { @@ -36,7 +36,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/getModerationEvent.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/getModerationEvent.ts index 7de567a73db..99c8bbe20ef 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/getModerationEvent.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/getModerationEvent.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -28,7 +28,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/getRecord.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/getRecord.ts index 48222d9d819..557945e2fbd 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/getRecord.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/getRecord.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -30,7 +30,7 @@ export interface HandlerError { error?: 'RecordNotFound' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/getRepo.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/getRepo.ts index 19911baa90a..ede9fcf3ce8 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/getRepo.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/getRepo.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -29,7 +29,7 @@ export interface HandlerError { error?: 'RepoNotFound' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/getSubjectStatus.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/getSubjectStatus.ts index 7315e51e8c2..d5976db70b1 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/getSubjectStatus.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/getSubjectStatus.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' import * as ComAtprotoRepoStrongRef from '../repo/strongRef' @@ -41,7 +41,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/listCommunicationTemplates.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/listCommunicationTemplates.ts index cb479533d39..843c228e6f9 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/listCommunicationTemplates.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/listCommunicationTemplates.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams {} @@ -31,7 +31,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts index 9972b688b71..df07cfaf2da 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -56,7 +56,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts index 6e1aea1f679..0cc21c25352 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -59,7 +59,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/searchRepos.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/searchRepos.ts index 1e7e1a36bb6..d1529956c17 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/searchRepos.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/searchRepos.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -38,7 +38,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/sendEmail.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/sendEmail.ts index f94cfb3a083..836fba39f79 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/sendEmail.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/sendEmail.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -41,7 +41,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts index 9e6140256ef..ebabffbccdb 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts index c378f421926..d6dc4a2dc25 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/updateCommunicationTemplate.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/updateCommunicationTemplate.ts index 5dc5cecda4a..73e079cfe58 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/updateCommunicationTemplate.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/updateCommunicationTemplate.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams {} @@ -44,7 +44,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/updateSubjectStatus.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/updateSubjectStatus.ts index 559ee948380..94df3041cf3 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/updateSubjectStatus.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/updateSubjectStatus.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' import * as ComAtprotoRepoStrongRef from '../repo/strongRef' @@ -48,7 +48,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/identity/resolveHandle.ts b/packages/ozone/src/lexicon/types/com/atproto/identity/resolveHandle.ts index ef90e99bb30..05019df6166 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/identity/resolveHandle.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/identity/resolveHandle.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The handle to resolve. */ @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/identity/updateHandle.ts b/packages/ozone/src/lexicon/types/com/atproto/identity/updateHandle.ts index 1f639c344e9..6782a68ed54 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/identity/updateHandle.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/identity/updateHandle.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/label/queryLabels.ts b/packages/ozone/src/lexicon/types/com/atproto/label/queryLabels.ts index 1d7f8a4def5..0c9d55a6961 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/label/queryLabels.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/label/queryLabels.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoLabelDefs from './defs' export interface QueryParams { @@ -39,7 +39,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/moderation/createReport.ts b/packages/ozone/src/lexicon/types/com/atproto/moderation/createReport.ts index 96aaf4a9c29..c1335eb3d1f 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/moderation/createReport.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/moderation/createReport.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoModerationDefs from './defs' import * as ComAtprotoAdminDefs from '../admin/defs' import * as ComAtprotoRepoStrongRef from '../repo/strongRef' @@ -52,7 +52,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/repo/applyWrites.ts b/packages/ozone/src/lexicon/types/com/atproto/repo/applyWrites.ts index 61d1e7c28e4..7bdb6dc2ed1 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/repo/applyWrites.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/repo/applyWrites.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/repo/createRecord.ts b/packages/ozone/src/lexicon/types/com/atproto/repo/createRecord.ts index df8c5d9e600..666b91c828d 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/repo/createRecord.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/repo/createRecord.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -49,7 +49,7 @@ export interface HandlerError { error?: 'InvalidSwap' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/repo/deleteRecord.ts b/packages/ozone/src/lexicon/types/com/atproto/repo/deleteRecord.ts index f45118a3769..65ee32d213d 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/repo/deleteRecord.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/repo/deleteRecord.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/repo/describeRepo.ts b/packages/ozone/src/lexicon/types/com/atproto/repo/describeRepo.ts index 7b8a2b995eb..38b9c01ef1c 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/repo/describeRepo.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/repo/describeRepo.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The handle or DID of the repo. */ @@ -37,7 +37,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/repo/getRecord.ts b/packages/ozone/src/lexicon/types/com/atproto/repo/getRecord.ts index 35c9b4b7166..345dde29a53 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/repo/getRecord.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/repo/getRecord.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The handle or DID of the repo. */ @@ -41,7 +41,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/repo/listRecords.ts b/packages/ozone/src/lexicon/types/com/atproto/repo/listRecords.ts index a6cf6abd1f3..f46f6eb0f7f 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/repo/listRecords.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/repo/listRecords.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The handle or DID of the repo. */ @@ -45,7 +45,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/repo/putRecord.ts b/packages/ozone/src/lexicon/types/com/atproto/repo/putRecord.ts index f10f773c1c4..de93e2e9cf7 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/repo/putRecord.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/repo/putRecord.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -51,7 +51,7 @@ export interface HandlerError { error?: 'InvalidSwap' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/repo/uploadBlob.ts b/packages/ozone/src/lexicon/types/com/atproto/repo/uploadBlob.ts index ad6002df925..febbbff9d16 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/repo/uploadBlob.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/repo/uploadBlob.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -34,7 +34,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/confirmEmail.ts b/packages/ozone/src/lexicon/types/com/atproto/server/confirmEmail.ts index ffaeeb8fe75..b667a04b996 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/confirmEmail.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/confirmEmail.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/createAccount.ts b/packages/ozone/src/lexicon/types/com/atproto/server/createAccount.ts index bbf2c009bf5..bceb61546cf 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/createAccount.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/createAccount.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -56,7 +56,7 @@ export interface HandlerError { | 'IncompatibleDidDoc' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/createAppPassword.ts b/packages/ozone/src/lexicon/types/com/atproto/server/createAppPassword.ts index 8e4a0a519e0..474846546fe 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/createAppPassword.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/createAppPassword.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -34,7 +34,7 @@ export interface HandlerError { error?: 'AccountTakedown' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/createInviteCode.ts b/packages/ozone/src/lexicon/types/com/atproto/server/createInviteCode.ts index acfac56ba76..9cfeacc7e28 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/createInviteCode.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/createInviteCode.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -37,7 +37,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/createInviteCodes.ts b/packages/ozone/src/lexicon/types/com/atproto/server/createInviteCodes.ts index 5887d77fada..eb6cd2bb1b1 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/createInviteCodes.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/createInviteCodes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -38,7 +38,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/createSession.ts b/packages/ozone/src/lexicon/types/com/atproto/server/createSession.ts index 2cd448703a6..3952959fe5e 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/createSession.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/createSession.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -45,7 +45,7 @@ export interface HandlerError { error?: 'AccountTakedown' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/deleteAccount.ts b/packages/ozone/src/lexicon/types/com/atproto/server/deleteAccount.ts index 37ddbba13e0..4fcec360a11 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/deleteAccount.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/deleteAccount.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/deleteSession.ts b/packages/ozone/src/lexicon/types/com/atproto/server/deleteSession.ts index e4244870425..82672f1d1c7 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/deleteSession.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/deleteSession.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/describeServer.ts b/packages/ozone/src/lexicon/types/com/atproto/server/describeServer.ts index bb574dba9ff..47be5b598b0 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/describeServer.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/describeServer.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts b/packages/ozone/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts index e387a5e38e4..2dc551a477c 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoServerDefs from './defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { error?: 'DuplicateCreate' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/getSession.ts b/packages/ozone/src/lexicon/types/com/atproto/server/getSession.ts index 4f95acf523d..5a8c40b947e 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/getSession.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/getSession.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -34,7 +34,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/listAppPasswords.ts b/packages/ozone/src/lexicon/types/com/atproto/server/listAppPasswords.ts index ebd74da9d39..241418d932d 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/listAppPasswords.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/listAppPasswords.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -31,7 +31,7 @@ export interface HandlerError { error?: 'AccountTakedown' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/refreshSession.ts b/packages/ozone/src/lexicon/types/com/atproto/server/refreshSession.ts index 35874f78a69..3adeb7fc20e 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/refreshSession.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/refreshSession.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -35,7 +35,7 @@ export interface HandlerError { error?: 'AccountTakedown' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/requestAccountDelete.ts b/packages/ozone/src/lexicon/types/com/atproto/server/requestAccountDelete.ts index e4244870425..82672f1d1c7 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/requestAccountDelete.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/requestAccountDelete.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/requestEmailConfirmation.ts b/packages/ozone/src/lexicon/types/com/atproto/server/requestEmailConfirmation.ts index e4244870425..82672f1d1c7 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/requestEmailConfirmation.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/requestEmailConfirmation.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/requestEmailUpdate.ts b/packages/ozone/src/lexicon/types/com/atproto/server/requestEmailUpdate.ts index 6876d44ca46..24dce3e12af 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/requestEmailUpdate.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/requestEmailUpdate.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -30,7 +30,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/requestPasswordReset.ts b/packages/ozone/src/lexicon/types/com/atproto/server/requestPasswordReset.ts index 47fb4bb62f3..d0f3f2ad769 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/requestPasswordReset.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/requestPasswordReset.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/reserveSigningKey.ts b/packages/ozone/src/lexicon/types/com/atproto/server/reserveSigningKey.ts index ad5a5a8758c..0de1220f4b5 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/reserveSigningKey.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/reserveSigningKey.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -38,7 +38,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/resetPassword.ts b/packages/ozone/src/lexicon/types/com/atproto/server/resetPassword.ts index 9e6ece3e4c4..38f63382cf0 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/resetPassword.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/resetPassword.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/revokeAppPassword.ts b/packages/ozone/src/lexicon/types/com/atproto/server/revokeAppPassword.ts index 4627f68eaa2..769ad6aa521 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/revokeAppPassword.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/revokeAppPassword.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/updateEmail.ts b/packages/ozone/src/lexicon/types/com/atproto/server/updateEmail.ts index c88bd3021b2..5473d7571e9 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/updateEmail.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/updateEmail.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/getBlob.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/getBlob.ts index 60750902472..b3980fca500 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/getBlob.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/getBlob.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -30,7 +30,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/getBlocks.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/getBlocks.ts index e73410efb41..f1b8ebe5db1 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/getBlocks.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/getBlocks.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -29,7 +29,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/getCheckout.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/getCheckout.ts index 63a657e56b9..51856b9088d 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/getCheckout.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/getCheckout.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -28,7 +28,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/getHead.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/getHead.ts index 586ae1a4189..adedd4cf211 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/getHead.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/getHead.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -34,7 +34,7 @@ export interface HandlerError { error?: 'HeadNotFound' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/getLatestCommit.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/getLatestCommit.ts index 9b91e878724..bbae68bbe76 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/getLatestCommit.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/getLatestCommit.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -35,7 +35,7 @@ export interface HandlerError { error?: 'RepoNotFound' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/getRecord.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/getRecord.ts index 297f0ac7794..e27878ff5e6 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/getRecord.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/getRecord.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -32,7 +32,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/getRepo.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/getRepo.ts index 495d31a1a22..e0ad53ded7c 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/getRepo.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/getRepo.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -30,7 +30,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/listBlobs.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/listBlobs.ts index b397bb3b3df..67a66577809 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/listBlobs.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/listBlobs.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -38,7 +38,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/listRepos.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/listRepos.ts index 783a8e314c2..12532860895 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/listRepos.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/listRepos.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { limit: number @@ -34,7 +34,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts index 3d310c1139a..f9498e6691d 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/requestCrawl.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/requestCrawl.ts index 87ef20d7297..2859e28fe69 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/requestCrawl.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/requestCrawl.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/temp/checkSignupQueue.ts b/packages/ozone/src/lexicon/types/com/atproto/temp/checkSignupQueue.ts index d2a431430a8..9486bce2b2b 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/temp/checkSignupQueue.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/temp/checkSignupQueue.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -32,7 +32,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/temp/fetchLabels.ts b/packages/ozone/src/lexicon/types/com/atproto/temp/fetchLabels.ts index 39341fd3a0e..0fbdeed1196 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/temp/fetchLabels.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/temp/fetchLabels.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoLabelDefs from '../label/defs' export interface QueryParams { @@ -34,7 +34,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/temp/importRepo.ts b/packages/ozone/src/lexicon/types/com/atproto/temp/importRepo.ts index d88361d9856..44bce41481c 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/temp/importRepo.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/temp/importRepo.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -32,7 +32,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/ozone/src/lexicon/types/com/atproto/temp/pushBlob.ts b/packages/ozone/src/lexicon/types/com/atproto/temp/pushBlob.ts index 97e890dbb14..d18a60b598f 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/temp/pushBlob.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/temp/pushBlob.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ diff --git a/packages/ozone/src/lexicon/types/com/atproto/temp/requestPhoneVerification.ts b/packages/ozone/src/lexicon/types/com/atproto/temp/requestPhoneVerification.ts index 5a295f701eb..c977500fc33 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/temp/requestPhoneVerification.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/temp/requestPhoneVerification.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/ozone/src/lexicon/types/com/atproto/temp/transferAccount.ts b/packages/ozone/src/lexicon/types/com/atproto/temp/transferAccount.ts index 86c1d750e07..565150caba2 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/temp/transferAccount.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/temp/transferAccount.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -49,7 +49,7 @@ export interface HandlerError { | 'IncompatibleDidDoc' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/api/app/bsky/actor/getProfile.ts b/packages/pds/src/api/app/bsky/actor/getProfile.ts index 732c7babe1a..d772b2b2ac2 100644 --- a/packages/pds/src/api/app/bsky/actor/getProfile.ts +++ b/packages/pds/src/api/app/bsky/actor/getProfile.ts @@ -4,9 +4,12 @@ import { authPassthru } from '../../../proxy' import { OutputSchema } from '../../../../lexicon/types/app/bsky/actor/getProfile' import { LocalViewer, - handleReadAfterWrite, LocalRecords, + handleReadAfterWrite, } from '../../../../read-after-write' +import { pipethrough } from '../../../../pipethrough' + +const METHOD_NSID = 'app.bsky.actor.getProfile' export default function (server: Server, ctx: AppContext) { server.app.bsky.actor.getProfile({ @@ -14,17 +17,22 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ req, auth, params }) => { const requester = auth.credentials.type === 'access' ? auth.credentials.did : null - const res = await ctx.appViewAgent.api.app.bsky.actor.getProfile( + const res = await pipethrough( + ctx.cfg.bskyAppView.url, + METHOD_NSID, params, requester ? await ctx.appviewAuthHeaders(requester) : authPassthru(req), ) - if (res.data.did === requester) { - return await handleReadAfterWrite(ctx, requester, res, getProfileMunge) - } - return { - encoding: 'application/json', - body: res.data, + if (!requester) { + return res } + return handleReadAfterWrite( + ctx, + METHOD_NSID, + requester, + res, + getProfileMunge, + ) }, }) } @@ -33,7 +41,9 @@ const getProfileMunge = async ( localViewer: LocalViewer, original: OutputSchema, local: LocalRecords, + requester: string, ): Promise => { if (!local.profile) return original + if (original.did !== requester) return original return localViewer.updateProfileDetailed(original, local.profile.record) } diff --git a/packages/pds/src/api/app/bsky/actor/getProfiles.ts b/packages/pds/src/api/app/bsky/actor/getProfiles.ts index 2f0f1405378..edbc880f43d 100644 --- a/packages/pds/src/api/app/bsky/actor/getProfiles.ts +++ b/packages/pds/src/api/app/bsky/actor/getProfiles.ts @@ -1,29 +1,34 @@ import AppContext from '../../../../context' import { Server } from '../../../../lexicon' import { OutputSchema } from '../../../../lexicon/types/app/bsky/actor/getProfiles' +import { pipethrough } from '../../../../pipethrough' import { LocalViewer, handleReadAfterWrite, LocalRecords, } from '../../../../read-after-write' +const METHOD_NSID = 'app.bsky.actor.getProfiles' + export default function (server: Server, ctx: AppContext) { server.app.bsky.actor.getProfiles({ auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.actor.getProfiles( + + const res = await pipethrough( + ctx.cfg.bskyAppView.url, + METHOD_NSID, params, await ctx.appviewAuthHeaders(requester), ) - const hasSelf = res.data.profiles.some((prof) => prof.did === requester) - if (hasSelf) { - return await handleReadAfterWrite(ctx, requester, res, getProfilesMunge) - } - return { - encoding: 'application/json', - body: res.data, - } + return handleReadAfterWrite( + ctx, + METHOD_NSID, + requester, + res, + getProfilesMunge, + ) }, }) } @@ -36,6 +41,7 @@ const getProfilesMunge = async ( ): Promise => { const localProf = local.profile if (!localProf) return original + const profiles = original.profiles.map((prof) => { if (prof.did !== requester) return prof return localViewer.updateProfileDetailed(prof, localProf.record) diff --git a/packages/pds/src/api/app/bsky/actor/getSuggestions.ts b/packages/pds/src/api/app/bsky/actor/getSuggestions.ts index 5fd8b260276..548d4f1611e 100644 --- a/packages/pds/src/api/app/bsky/actor/getSuggestions.ts +++ b/packages/pds/src/api/app/bsky/actor/getSuggestions.ts @@ -1,19 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.actor.getSuggestions({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.actor.getSuggestions( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.actor.getSuggestions', params, await ctx.appviewAuthHeaders(requester), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/actor/searchActors.ts b/packages/pds/src/api/app/bsky/actor/searchActors.ts index 7c184de1116..57f502acb3c 100644 --- a/packages/pds/src/api/app/bsky/actor/searchActors.ts +++ b/packages/pds/src/api/app/bsky/actor/searchActors.ts @@ -1,19 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.actor.searchActors({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.actor.searchActors( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.actor.searchActors', params, await ctx.appviewAuthHeaders(requester), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/actor/searchActorsTypeahead.ts b/packages/pds/src/api/app/bsky/actor/searchActorsTypeahead.ts index c1a8738488d..dbf57900509 100644 --- a/packages/pds/src/api/app/bsky/actor/searchActorsTypeahead.ts +++ b/packages/pds/src/api/app/bsky/actor/searchActorsTypeahead.ts @@ -1,20 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.actor.searchActorsTypeahead({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did - const res = - await ctx.appViewAgent.api.app.bsky.actor.searchActorsTypeahead( - params, - await ctx.appviewAuthHeaders(requester), - ) - return { - encoding: 'application/json', - body: res.data, - } + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.actor.searchActorsTypeahead', + params, + await ctx.appviewAuthHeaders(requester), + ) }, }) } diff --git a/packages/pds/src/api/app/bsky/feed/getActorFeeds.ts b/packages/pds/src/api/app/bsky/feed/getActorFeeds.ts index 384d68500d4..ca5d8d5d48e 100644 --- a/packages/pds/src/api/app/bsky/feed/getActorFeeds.ts +++ b/packages/pds/src/api/app/bsky/feed/getActorFeeds.ts @@ -1,19 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getActorFeeds({ auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.feed.getActorFeeds( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.feed.getActorFeeds', params, await ctx.appviewAuthHeaders(requester), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/feed/getActorLikes.ts b/packages/pds/src/api/app/bsky/feed/getActorLikes.ts index 07fb66fd828..4ba8bb49f49 100644 --- a/packages/pds/src/api/app/bsky/feed/getActorLikes.ts +++ b/packages/pds/src/api/app/bsky/feed/getActorLikes.ts @@ -7,6 +7,9 @@ import { handleReadAfterWrite, LocalRecords, } from '../../../../read-after-write' +import { pipethrough } from '../../../../pipethrough' + +const METHOD_NSID = 'app.bsky.feed.getActorLikes' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getActorLikes({ @@ -14,18 +17,24 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ req, params, auth }) => { const requester = auth.credentials.type === 'access' ? auth.credentials.did : null - - const res = await ctx.appViewAgent.api.app.bsky.feed.getActorLikes( + const res = await pipethrough( + ctx.cfg.bskyAppView.url, + METHOD_NSID, params, requester ? await ctx.appviewAuthHeaders(requester) : authPassthru(req), ) - if (requester) { - return await handleReadAfterWrite(ctx, requester, res, getAuthorMunge) - } - return { - encoding: 'application/json', - body: res.data, + + if (!requester) { + return res } + + return await handleReadAfterWrite( + ctx, + METHOD_NSID, + requester, + res, + getAuthorMunge, + ) }, }) } diff --git a/packages/pds/src/api/app/bsky/feed/getAuthorFeed.ts b/packages/pds/src/api/app/bsky/feed/getAuthorFeed.ts index 17ae8f0ac10..f142a6c34e9 100644 --- a/packages/pds/src/api/app/bsky/feed/getAuthorFeed.ts +++ b/packages/pds/src/api/app/bsky/feed/getAuthorFeed.ts @@ -8,6 +8,9 @@ import { handleReadAfterWrite, LocalRecords, } from '../../../../read-after-write' +import { pipethrough } from '../../../../pipethrough' + +const METHOD_NSID = 'app.bsky.feed.getAuthorFeed' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getAuthorFeed({ @@ -15,17 +18,22 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ req, params, auth }) => { const requester = auth.credentials.type === 'access' ? auth.credentials.did : null - const res = await ctx.appViewAgent.api.app.bsky.feed.getAuthorFeed( + const res = await pipethrough( + ctx.cfg.bskyAppView.url, + METHOD_NSID, params, requester ? await ctx.appviewAuthHeaders(requester) : authPassthru(req), ) - if (requester) { - return await handleReadAfterWrite(ctx, requester, res, getAuthorMunge) - } - return { - encoding: 'application/json', - body: res.data, + if (!requester) { + return res } + return await handleReadAfterWrite( + ctx, + METHOD_NSID, + requester, + res, + getAuthorMunge, + ) }, }) } diff --git a/packages/pds/src/api/app/bsky/feed/getFeed.ts b/packages/pds/src/api/app/bsky/feed/getFeed.ts index dd42e81fb94..ea63cf1bd47 100644 --- a/packages/pds/src/api/app/bsky/feed/getFeed.ts +++ b/packages/pds/src/api/app/bsky/feed/getFeed.ts @@ -1,6 +1,6 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { noUndefinedVals } from '@atproto/common' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getFeed({ @@ -20,18 +20,12 @@ export default function (server: Server, ctx: AppContext) { // forward accept-language header to upstream services serviceAuthHeaders.headers['accept-language'] = req.headers['accept-language'] - const res = await ctx.appViewAgent.api.app.bsky.feed.getFeed( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.feed.getFeed', params, serviceAuthHeaders, ) - - return { - encoding: 'application/json', - body: res.data, - headers: noUndefinedVals({ - 'content-language': res.headers['content-language'], - }), - } }, }) } diff --git a/packages/pds/src/api/app/bsky/feed/getFeedGenerator.ts b/packages/pds/src/api/app/bsky/feed/getFeedGenerator.ts index 57c4731db53..deb5025f0fe 100644 --- a/packages/pds/src/api/app/bsky/feed/getFeedGenerator.ts +++ b/packages/pds/src/api/app/bsky/feed/getFeedGenerator.ts @@ -1,19 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getFeedGenerator({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.feed.getFeedGenerator( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.feed.getFeedGenerator', params, await ctx.appviewAuthHeaders(requester), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/feed/getFeedGenerators.ts b/packages/pds/src/api/app/bsky/feed/getFeedGenerators.ts index 1370fbbd6f3..12755d0feea 100644 --- a/packages/pds/src/api/app/bsky/feed/getFeedGenerators.ts +++ b/packages/pds/src/api/app/bsky/feed/getFeedGenerators.ts @@ -1,19 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getFeedGenerators({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.feed.getFeedGenerators( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.feed.getFeedGenerators', params, await ctx.appviewAuthHeaders(requester), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/feed/getLikes.ts b/packages/pds/src/api/app/bsky/feed/getLikes.ts index ad656dfbd4c..cb5f2cd6fbc 100644 --- a/packages/pds/src/api/app/bsky/feed/getLikes.ts +++ b/packages/pds/src/api/app/bsky/feed/getLikes.ts @@ -1,19 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getLikes({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.feed.getLikes( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.feed.getLikes', params, await ctx.appviewAuthHeaders(requester), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/feed/getListFeed.ts b/packages/pds/src/api/app/bsky/feed/getListFeed.ts index 06e2abcbfe5..cbbc757dd52 100644 --- a/packages/pds/src/api/app/bsky/feed/getListFeed.ts +++ b/packages/pds/src/api/app/bsky/feed/getListFeed.ts @@ -1,19 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getListFeed({ auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.feed.getListFeed( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.feed.getListFeed', params, await ctx.appviewAuthHeaders(requester), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/feed/getPostThread.ts b/packages/pds/src/api/app/bsky/feed/getPostThread.ts index 00bf0b01d82..d4aec88970a 100644 --- a/packages/pds/src/api/app/bsky/feed/getPostThread.ts +++ b/packages/pds/src/api/app/bsky/feed/getPostThread.ts @@ -1,6 +1,5 @@ import { AtUri } from '@atproto/syntax' -import { AppBskyFeedGetPostThread } from '@atproto/api' -import { Headers } from '@atproto/xrpc' +import { Headers, XRPCError } from '@atproto/xrpc' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { authPassthru } from '../../../proxy' @@ -17,10 +16,14 @@ import { LocalViewer, getLocalLag, getRepoRev, - handleReadAfterWrite, LocalRecords, RecordDescript, + handleReadAfterWrite, + formatMungedResponse, } from '../../../../read-after-write' +import { pipethrough } from '../../../../pipethrough' + +const METHOD_NSID = 'app.bsky.feed.getPostThread' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getPostThread({ @@ -30,31 +33,31 @@ export default function (server: Server, ctx: AppContext) { auth.credentials.type === 'access' ? auth.credentials.did : null if (!requester) { - const res = await ctx.appViewAgent.api.app.bsky.feed.getPostThread( + return pipethrough( + ctx.cfg.bskyAppView.url, + METHOD_NSID, params, authPassthru(req), ) - - return { - encoding: 'application/json', - body: res.data, - } } try { - const res = await ctx.appViewAgent.api.app.bsky.feed.getPostThread( + const res = await pipethrough( + ctx.cfg.bskyAppView.url, + METHOD_NSID, params, await ctx.appviewAuthHeaders(requester), ) return await handleReadAfterWrite( ctx, + METHOD_NSID, requester, res, getPostThreadMunge, ) } catch (err) { - if (err instanceof AppBskyFeedGetPostThread.NotFoundError) { + if (err instanceof XRPCError && err.error === 'NotFound') { const headers = err.headers const keypair = await ctx.actorStore.keypair(requester) const local = await ctx.actorStore.read(requester, (store) => { @@ -70,15 +73,7 @@ export default function (server: Server, ctx: AppContext) { if (local === null) { throw err } else { - return { - encoding: 'application/json', - body: local.data, - headers: local.lag - ? { - 'Atproto-Upstream-Lag': local.lag.toString(10), - } - : undefined, - } + return formatMungedResponse(local.data, local.lag) } } else { throw err diff --git a/packages/pds/src/api/app/bsky/feed/getPosts.ts b/packages/pds/src/api/app/bsky/feed/getPosts.ts index f04927a4997..5caa986f9f4 100644 --- a/packages/pds/src/api/app/bsky/feed/getPosts.ts +++ b/packages/pds/src/api/app/bsky/feed/getPosts.ts @@ -1,19 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getPosts({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.feed.getPosts( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.feed.getPosts', params, await ctx.appviewAuthHeaders(requester), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/feed/getRepostedBy.ts b/packages/pds/src/api/app/bsky/feed/getRepostedBy.ts index e797967fc2a..dcb00d5ce64 100644 --- a/packages/pds/src/api/app/bsky/feed/getRepostedBy.ts +++ b/packages/pds/src/api/app/bsky/feed/getRepostedBy.ts @@ -1,19 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getRepostedBy({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.feed.getRepostedBy( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.feed.getRepostedBy', params, await ctx.appviewAuthHeaders(requester), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/feed/getSuggestedFeeds.ts b/packages/pds/src/api/app/bsky/feed/getSuggestedFeeds.ts index 17fbf947471..ee1161ca0ff 100644 --- a/packages/pds/src/api/app/bsky/feed/getSuggestedFeeds.ts +++ b/packages/pds/src/api/app/bsky/feed/getSuggestedFeeds.ts @@ -1,19 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getSuggestedFeeds({ auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.feed.getSuggestedFeeds( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.feed.getSuggestedFeeds', params, await ctx.appviewAuthHeaders(requester), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/feed/getTimeline.ts b/packages/pds/src/api/app/bsky/feed/getTimeline.ts index 6139432580a..ffce48244b5 100644 --- a/packages/pds/src/api/app/bsky/feed/getTimeline.ts +++ b/packages/pds/src/api/app/bsky/feed/getTimeline.ts @@ -6,17 +6,28 @@ import { handleReadAfterWrite, LocalRecords, } from '../../../../read-after-write' +import { pipethrough } from '../../../../pipethrough' + +const METHOD_NSID = 'app.bsky.feed.getTimeline' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getTimeline({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.feed.getTimeline( + const res = await pipethrough( + ctx.cfg.bskyAppView.url, + METHOD_NSID, params, await ctx.appviewAuthHeaders(requester), ) - return await handleReadAfterWrite(ctx, requester, res, getTimelineMunge) + return await handleReadAfterWrite( + ctx, + METHOD_NSID, + requester, + res, + getTimelineMunge, + ) }, }) } diff --git a/packages/pds/src/api/app/bsky/feed/searchPosts.ts b/packages/pds/src/api/app/bsky/feed/searchPosts.ts index e9942432fde..24bc33f6034 100644 --- a/packages/pds/src/api/app/bsky/feed/searchPosts.ts +++ b/packages/pds/src/api/app/bsky/feed/searchPosts.ts @@ -1,19 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' 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( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.feed.searchPosts', params, await ctx.appviewAuthHeaders(requester), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/graph/getBlocks.ts b/packages/pds/src/api/app/bsky/graph/getBlocks.ts index ff1f299f43f..f50066e5e8f 100644 --- a/packages/pds/src/api/app/bsky/graph/getBlocks.ts +++ b/packages/pds/src/api/app/bsky/graph/getBlocks.ts @@ -1,19 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.getBlocks({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.graph.getBlocks( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.graph.getBlocks', params, await ctx.appviewAuthHeaders(requester), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/graph/getFollowers.ts b/packages/pds/src/api/app/bsky/graph/getFollowers.ts index 18f1e0df397..4a07284b525 100644 --- a/packages/pds/src/api/app/bsky/graph/getFollowers.ts +++ b/packages/pds/src/api/app/bsky/graph/getFollowers.ts @@ -1,6 +1,7 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { authPassthru } from '../../../proxy' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.getFollowers({ @@ -8,14 +9,12 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ req, params, auth }) => { const requester = auth.credentials.type === 'access' ? auth.credentials.did : null - const res = await ctx.appViewAgent.api.app.bsky.graph.getFollowers( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.graph.getFollowers', params, requester ? await ctx.appviewAuthHeaders(requester) : authPassthru(req), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/graph/getFollows.ts b/packages/pds/src/api/app/bsky/graph/getFollows.ts index 91955965ed6..2f3c2f2041a 100644 --- a/packages/pds/src/api/app/bsky/graph/getFollows.ts +++ b/packages/pds/src/api/app/bsky/graph/getFollows.ts @@ -1,6 +1,7 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { authPassthru } from '../../../proxy' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.getFollows({ @@ -8,14 +9,12 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ req, params, auth }) => { const requester = auth.credentials.type === 'access' ? auth.credentials.did : null - const res = await ctx.appViewAgent.api.app.bsky.graph.getFollows( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.graph.getFollows', params, requester ? await ctx.appviewAuthHeaders(requester) : authPassthru(req), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/graph/getList.ts b/packages/pds/src/api/app/bsky/graph/getList.ts index fb5776f5df2..60ea41cfd2f 100644 --- a/packages/pds/src/api/app/bsky/graph/getList.ts +++ b/packages/pds/src/api/app/bsky/graph/getList.ts @@ -1,19 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.getList({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.graph.getList( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.graph.getList', params, await ctx.appviewAuthHeaders(requester), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/graph/getListBlocks.ts b/packages/pds/src/api/app/bsky/graph/getListBlocks.ts index 376de0ba914..fed639e1dc8 100644 --- a/packages/pds/src/api/app/bsky/graph/getListBlocks.ts +++ b/packages/pds/src/api/app/bsky/graph/getListBlocks.ts @@ -1,19 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.getListBlocks({ auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.graph.getListBlocks( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.graph.getListBlocks', params, await ctx.appviewAuthHeaders(requester), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/graph/getListMutes.ts b/packages/pds/src/api/app/bsky/graph/getListMutes.ts index c489124642c..f3856a7fdc0 100644 --- a/packages/pds/src/api/app/bsky/graph/getListMutes.ts +++ b/packages/pds/src/api/app/bsky/graph/getListMutes.ts @@ -1,19 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.getListMutes({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.graph.getListMutes( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.graph.getListMutes', params, await ctx.appviewAuthHeaders(requester), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/graph/getLists.ts b/packages/pds/src/api/app/bsky/graph/getLists.ts index 61a1cb89079..4af580e280c 100644 --- a/packages/pds/src/api/app/bsky/graph/getLists.ts +++ b/packages/pds/src/api/app/bsky/graph/getLists.ts @@ -1,19 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.getLists({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.graph.getLists( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.graph.getLists', params, await ctx.appviewAuthHeaders(requester), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/graph/getMutes.ts b/packages/pds/src/api/app/bsky/graph/getMutes.ts index 0dc0e72412c..103c0bf4f2f 100644 --- a/packages/pds/src/api/app/bsky/graph/getMutes.ts +++ b/packages/pds/src/api/app/bsky/graph/getMutes.ts @@ -1,19 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.getMutes({ auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did - const res = await ctx.appViewAgent.api.app.bsky.graph.getMutes( + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.graph.getMutes', params, await ctx.appviewAuthHeaders(requester), ) - return { - encoding: 'application/json', - body: res.data, - } }, }) } diff --git a/packages/pds/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts b/packages/pds/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts index 0a11361f05c..b4422b7090e 100644 --- a/packages/pds/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts +++ b/packages/pds/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts @@ -1,20 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.getSuggestedFollowsByActor({ auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did - const res = - await ctx.appViewAgent.api.app.bsky.graph.getSuggestedFollowsByActor( - params, - await ctx.appviewAuthHeaders(requester), - ) - return { - encoding: 'application/json', - body: res.data, - } + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.graph.getSuggestedFollowsByActor', + params, + await ctx.appviewAuthHeaders(requester), + ) }, }) } diff --git a/packages/pds/src/api/app/bsky/notification/getUnreadCount.ts b/packages/pds/src/api/app/bsky/notification/getUnreadCount.ts index bca4bf3d46f..0827f47bb9e 100644 --- a/packages/pds/src/api/app/bsky/notification/getUnreadCount.ts +++ b/packages/pds/src/api/app/bsky/notification/getUnreadCount.ts @@ -1,20 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.notification.getUnreadCount({ auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did - const res = - await ctx.appViewAgent.api.app.bsky.notification.getUnreadCount( - params, - await ctx.appviewAuthHeaders(requester), - ) - return { - encoding: 'application/json', - body: res.data, - } + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.notification.getUnreadCount', + params, + await ctx.appviewAuthHeaders(requester), + ) }, }) } diff --git a/packages/pds/src/api/app/bsky/notification/listNotifications.ts b/packages/pds/src/api/app/bsky/notification/listNotifications.ts index 4a514ae03f4..ad5d8635b41 100644 --- a/packages/pds/src/api/app/bsky/notification/listNotifications.ts +++ b/packages/pds/src/api/app/bsky/notification/listNotifications.ts @@ -1,20 +1,18 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.app.bsky.notification.listNotifications({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did - const res = - await ctx.appViewAgent.api.app.bsky.notification.listNotifications( - params, - await ctx.appviewAuthHeaders(requester), - ) - return { - encoding: 'application/json', - body: res.data, - } + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.notification.listNotifications', + params, + await ctx.appviewAuthHeaders(requester), + ) }, }) } diff --git a/packages/pds/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts b/packages/pds/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts index 08466ed9f5c..56f0c6134af 100644 --- a/packages/pds/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +++ b/packages/pds/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts @@ -1,5 +1,6 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' // THIS IS A TEMPORARY UNSPECCED ROUTE export default function (server: Server, ctx: AppContext) { @@ -7,15 +8,12 @@ export default function (server: Server, ctx: AppContext) { auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did - const res = - await ctx.appViewAgent.api.app.bsky.unspecced.getPopularFeedGenerators( - params, - await ctx.appviewAuthHeaders(requester), - ) - return { - encoding: 'application/json', - body: res.data, - } + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.unspecced.getPopularFeedGenerators', + params, + await ctx.appviewAuthHeaders(requester), + ) }, }) } diff --git a/packages/pds/src/api/app/bsky/unspecced/getTaggedSuggestions.ts b/packages/pds/src/api/app/bsky/unspecced/getTaggedSuggestions.ts index cd43177b83d..80c868490c4 100644 --- a/packages/pds/src/api/app/bsky/unspecced/getTaggedSuggestions.ts +++ b/packages/pds/src/api/app/bsky/unspecced/getTaggedSuggestions.ts @@ -1,5 +1,6 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { pipethrough } from '../../../../pipethrough' // THIS IS A TEMPORARY UNSPECCED ROUTE export default function (server: Server, ctx: AppContext) { @@ -7,15 +8,12 @@ export default function (server: Server, ctx: AppContext) { auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did - const res = - await ctx.appViewAgent.api.app.bsky.unspecced.getTaggedSuggestions( - params, - await ctx.appviewAuthHeaders(requester), - ) - return { - encoding: 'application/json', - body: res.data, - } + return pipethrough( + ctx.cfg.bskyAppView.url, + 'app.bsky.unspecced.getTaggedSuggestions', + params, + await ctx.appviewAuthHeaders(requester), + ) }, }) } diff --git a/packages/pds/src/api/com/atproto/repo/getRecord.ts b/packages/pds/src/api/com/atproto/repo/getRecord.ts index 68f73d38b12..20abb3c46a8 100644 --- a/packages/pds/src/api/com/atproto/repo/getRecord.ts +++ b/packages/pds/src/api/com/atproto/repo/getRecord.ts @@ -2,6 +2,7 @@ import { AtUri } from '@atproto/syntax' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { InvalidRequestError } from '@atproto/xrpc-server' +import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { server.com.atproto.repo.getRecord(async ({ params }) => { @@ -27,10 +28,10 @@ export default function (server: Server, ctx: AppContext) { } } - const res = await ctx.appViewAgent.api.com.atproto.repo.getRecord(params) - return { - encoding: 'application/json', - body: res.data, - } + return await pipethrough( + ctx.cfg.bskyAppView.url, + 'com.atproto.repo.getRecord', + params, + ) }) } diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/getPreferences.ts b/packages/pds/src/lexicon/types/app/bsky/actor/getPreferences.ts index 88d78a57cba..305e80484be 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/getPreferences.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/getPreferences.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams {} @@ -31,7 +31,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/getProfile.ts b/packages/pds/src/lexicon/types/app/bsky/actor/getProfile.ts index 802afda5361..be58c73a233 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/getProfile.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/getProfile.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams { @@ -28,7 +28,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/getProfiles.ts b/packages/pds/src/lexicon/types/app/bsky/actor/getProfiles.ts index 2549b264e33..16438505654 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/getProfiles.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/getProfiles.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams { @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/getSuggestions.ts b/packages/pds/src/lexicon/types/app/bsky/actor/getSuggestions.ts index a6d4d6102af..33b89a18bfa 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/getSuggestions.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/getSuggestions.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/putPreferences.ts b/packages/pds/src/lexicon/types/app/bsky/actor/putPreferences.ts index 1e5ee2d834e..670e752fea3 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/putPreferences.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/putPreferences.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/searchActors.ts b/packages/pds/src/lexicon/types/app/bsky/actor/searchActors.ts index f072b8a4d04..dcda0c41854 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/searchActors.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/searchActors.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams { @@ -39,7 +39,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts b/packages/pds/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts index 0cf56753db2..0198b23d790 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/searchActorsTypeahead.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts b/packages/pds/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts index d329bf20a5a..5bf8699a3ca 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/describeFeedGenerator.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -32,7 +32,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getActorFeeds.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getActorFeeds.ts index 3e930cbe201..0b8afff4ec8 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getActorFeeds.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getActorFeeds.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -36,7 +36,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getActorLikes.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getActorLikes.ts index df2f291e1a7..da315ae33c7 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getActorLikes.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getActorLikes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { error?: 'BlockedActor' | 'BlockedByActor' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts index 25f51f6fe5f..8f8e038c71f 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -43,7 +43,7 @@ export interface HandlerError { error?: 'BlockedActor' | 'BlockedByActor' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getFeed.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getFeed.ts index e72b1010aea..e03913a6fb3 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getFeed.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getFeed.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { error?: 'UnknownFeed' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts index fab3b30c316..b65a5151d46 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts index d7e082f2362..21963a91e2e 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerators.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts index 1c8f349b42b..ab1911ecb87 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { error?: 'UnknownFeed' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getLikes.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getLikes.ts index d581f5bfa9c..c7e8c860bd6 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getLikes.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getLikes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -39,7 +39,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getListFeed.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getListFeed.ts index e24c3f8ed22..ab157788f72 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getListFeed.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getListFeed.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { error?: 'UnknownList' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getPostThread.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getPostThread.ts index 61de94b729d..9e81da0eff6 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getPostThread.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getPostThread.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -40,7 +40,7 @@ export interface HandlerError { error?: 'NotFound' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getPosts.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getPosts.ts index 4282f5d349f..439e2132201 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getPosts.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getPosts.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getRepostedBy.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getRepostedBy.ts index 0b9c1a6f68b..6dea2b753c7 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getRepostedBy.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getRepostedBy.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -39,7 +39,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getSuggestedFeeds.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getSuggestedFeeds.ts index 9b271335466..d1ec590f33d 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getSuggestedFeeds.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getSuggestedFeeds.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getTimeline.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getTimeline.ts index 832caf5c6f7..f37def5808e 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getTimeline.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getTimeline.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -36,7 +36,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/searchPosts.ts b/packages/pds/src/lexicon/types/app/bsky/feed/searchPosts.ts index 36ac7cbb67d..9dae079c226 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/searchPosts.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/searchPosts.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { @@ -41,7 +41,7 @@ export interface HandlerError { error?: 'BadQueryString' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getBlocks.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getBlocks.ts index d380a14880a..1fc9cd8ce37 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getBlocks.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getBlocks.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getFollowers.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getFollowers.ts index b337be52c1b..f5645eaef29 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getFollowers.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getFollowers.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getFollows.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getFollows.ts index 71e9ca0270c..b9bd249da45 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getFollows.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getFollows.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getList.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getList.ts index fc45dd20985..6b30cd7faa9 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getList.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getList.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { @@ -37,7 +37,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getListBlocks.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getListBlocks.ts index 04cca70b44d..7399a14fadc 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getListBlocks.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getListBlocks.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getListMutes.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getListMutes.ts index 04cca70b44d..7399a14fadc 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getListMutes.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getListMutes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getLists.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getLists.ts index 8acf9362c00..6bcb3134a47 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getLists.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getLists.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { @@ -36,7 +36,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getMutes.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getMutes.ts index 0034095b975..f450393522d 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getMutes.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getMutes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getRelationships.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getRelationships.ts index 32a27434782..0125414ccd4 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getRelationships.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getRelationships.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { @@ -40,7 +40,7 @@ export interface HandlerError { error?: 'ActorNotFound' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts index a2245846fd2..8f310334d0a 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/muteActor.ts b/packages/pds/src/lexicon/types/app/bsky/graph/muteActor.ts index 52d1b864989..baa9844046a 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/muteActor.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/muteActor.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/muteActorList.ts b/packages/pds/src/lexicon/types/app/bsky/graph/muteActorList.ts index bf803f388af..6a68f680a1c 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/muteActorList.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/muteActorList.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/unmuteActor.ts b/packages/pds/src/lexicon/types/app/bsky/graph/unmuteActor.ts index 52d1b864989..baa9844046a 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/unmuteActor.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/unmuteActor.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/unmuteActorList.ts b/packages/pds/src/lexicon/types/app/bsky/graph/unmuteActorList.ts index bf803f388af..6a68f680a1c 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/unmuteActorList.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/unmuteActorList.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/app/bsky/notification/getUnreadCount.ts b/packages/pds/src/lexicon/types/app/bsky/notification/getUnreadCount.ts index 6cf3c84beb5..eae30df7c1b 100644 --- a/packages/pds/src/lexicon/types/app/bsky/notification/getUnreadCount.ts +++ b/packages/pds/src/lexicon/types/app/bsky/notification/getUnreadCount.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { seenAt?: string @@ -32,7 +32,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/notification/listNotifications.ts b/packages/pds/src/lexicon/types/app/bsky/notification/listNotifications.ts index b50d6e8282e..d494494e569 100644 --- a/packages/pds/src/lexicon/types/app/bsky/notification/listNotifications.ts +++ b/packages/pds/src/lexicon/types/app/bsky/notification/listNotifications.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' @@ -38,7 +38,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/notification/registerPush.ts b/packages/pds/src/lexicon/types/app/bsky/notification/registerPush.ts index 9923aeb058e..cce8f95839d 100644 --- a/packages/pds/src/lexicon/types/app/bsky/notification/registerPush.ts +++ b/packages/pds/src/lexicon/types/app/bsky/notification/registerPush.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/app/bsky/notification/updateSeen.ts b/packages/pds/src/lexicon/types/app/bsky/notification/updateSeen.ts index 136191edc40..93db017a152 100644 --- a/packages/pds/src/lexicon/types/app/bsky/notification/updateSeen.ts +++ b/packages/pds/src/lexicon/types/app/bsky/notification/updateSeen.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts b/packages/pds/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts index 97937e926c2..02f19f3cc6a 100644 --- a/packages/pds/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts +++ b/packages/pds/src/lexicon/types/app/bsky/unspecced/getPopularFeedGenerators.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from '../feed/defs' export interface QueryParams { @@ -36,7 +36,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/unspecced/getTaggedSuggestions.ts b/packages/pds/src/lexicon/types/app/bsky/unspecced/getTaggedSuggestions.ts index e6319c54b4e..a03f442140d 100644 --- a/packages/pds/src/lexicon/types/app/bsky/unspecced/getTaggedSuggestions.ts +++ b/packages/pds/src/lexicon/types/app/bsky/unspecced/getTaggedSuggestions.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -30,7 +30,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/unspecced/searchActorsSkeleton.ts b/packages/pds/src/lexicon/types/app/bsky/unspecced/searchActorsSkeleton.ts index 5c45b9fb622..4634407b890 100644 --- a/packages/pds/src/lexicon/types/app/bsky/unspecced/searchActorsSkeleton.ts +++ b/packages/pds/src/lexicon/types/app/bsky/unspecced/searchActorsSkeleton.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyUnspeccedDefs from './defs' export interface QueryParams { @@ -43,7 +43,7 @@ export interface HandlerError { error?: 'BadQueryString' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/app/bsky/unspecced/searchPostsSkeleton.ts b/packages/pds/src/lexicon/types/app/bsky/unspecced/searchPostsSkeleton.ts index 15532087b82..860d4e8407c 100644 --- a/packages/pds/src/lexicon/types/app/bsky/unspecced/searchPostsSkeleton.ts +++ b/packages/pds/src/lexicon/types/app/bsky/unspecced/searchPostsSkeleton.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyUnspeccedDefs from './defs' export interface QueryParams { @@ -41,7 +41,7 @@ export interface HandlerError { error?: 'BadQueryString' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/createCommunicationTemplate.ts b/packages/pds/src/lexicon/types/com/atproto/admin/createCommunicationTemplate.ts index d42a8f2ef1d..b910b7987b4 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/createCommunicationTemplate.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/createCommunicationTemplate.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams {} @@ -41,7 +41,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/deleteAccount.ts b/packages/pds/src/lexicon/types/com/atproto/admin/deleteAccount.ts index 13e68eb5c7d..003c1b5ebcd 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/deleteAccount.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/deleteAccount.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/deleteCommunicationTemplate.ts b/packages/pds/src/lexicon/types/com/atproto/admin/deleteCommunicationTemplate.ts index 4bc6ec86fe4..c5ae5cd469f 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/deleteCommunicationTemplate.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/deleteCommunicationTemplate.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts b/packages/pds/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts index 62864923dfd..68c6503d95e 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/disableAccountInvites.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts b/packages/pds/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts index 2b64371f1ed..2bf8de35583 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/disableInviteCodes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts b/packages/pds/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts index df44702b51c..3c0acabd0eb 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' import * as ComAtprotoRepoStrongRef from '../repo/strongRef' @@ -53,7 +53,7 @@ export interface HandlerError { error?: 'SubjectHasAction' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts b/packages/pds/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts index fb3aa8b8375..3f2836e7142 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/enableAccountInvites.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/getAccountInfo.ts b/packages/pds/src/lexicon/types/com/atproto/admin/getAccountInfo.ts index 88a2b17a4b8..c7b840a153d 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/getAccountInfo.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/getAccountInfo.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -28,7 +28,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/getAccountInfos.ts b/packages/pds/src/lexicon/types/com/atproto/admin/getAccountInfos.ts index 46d917293a8..99ef44a99f5 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/getAccountInfos.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/getAccountInfos.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/getInviteCodes.ts b/packages/pds/src/lexicon/types/com/atproto/admin/getInviteCodes.ts index 1eb099aae66..d68b97d775a 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/getInviteCodes.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/getInviteCodes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoServerDefs from '../server/defs' export interface QueryParams { @@ -36,7 +36,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/getModerationEvent.ts b/packages/pds/src/lexicon/types/com/atproto/admin/getModerationEvent.ts index 7de567a73db..99c8bbe20ef 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/getModerationEvent.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/getModerationEvent.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -28,7 +28,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/getRecord.ts b/packages/pds/src/lexicon/types/com/atproto/admin/getRecord.ts index 48222d9d819..557945e2fbd 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/getRecord.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/getRecord.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -30,7 +30,7 @@ export interface HandlerError { error?: 'RecordNotFound' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/getRepo.ts b/packages/pds/src/lexicon/types/com/atproto/admin/getRepo.ts index 19911baa90a..ede9fcf3ce8 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/getRepo.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/getRepo.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -29,7 +29,7 @@ export interface HandlerError { error?: 'RepoNotFound' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/getSubjectStatus.ts b/packages/pds/src/lexicon/types/com/atproto/admin/getSubjectStatus.ts index 7315e51e8c2..d5976db70b1 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/getSubjectStatus.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/getSubjectStatus.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' import * as ComAtprotoRepoStrongRef from '../repo/strongRef' @@ -41,7 +41,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/listCommunicationTemplates.ts b/packages/pds/src/lexicon/types/com/atproto/admin/listCommunicationTemplates.ts index cb479533d39..843c228e6f9 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/listCommunicationTemplates.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/listCommunicationTemplates.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams {} @@ -31,7 +31,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts b/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts index 9972b688b71..df07cfaf2da 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -56,7 +56,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts b/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts index 6e1aea1f679..0cc21c25352 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -59,7 +59,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/searchRepos.ts b/packages/pds/src/lexicon/types/com/atproto/admin/searchRepos.ts index 1e7e1a36bb6..d1529956c17 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/searchRepos.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/searchRepos.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams { @@ -38,7 +38,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/sendEmail.ts b/packages/pds/src/lexicon/types/com/atproto/admin/sendEmail.ts index f94cfb3a083..836fba39f79 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/sendEmail.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/sendEmail.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -41,7 +41,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts b/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts index 9e6140256ef..ebabffbccdb 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountEmail.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts b/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts index c378f421926..d6dc4a2dc25 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountHandle.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/updateCommunicationTemplate.ts b/packages/pds/src/lexicon/types/com/atproto/admin/updateCommunicationTemplate.ts index 5dc5cecda4a..73e079cfe58 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/updateCommunicationTemplate.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/updateCommunicationTemplate.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' export interface QueryParams {} @@ -44,7 +44,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/updateSubjectStatus.ts b/packages/pds/src/lexicon/types/com/atproto/admin/updateSubjectStatus.ts index 559ee948380..94df3041cf3 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/updateSubjectStatus.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/updateSubjectStatus.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoAdminDefs from './defs' import * as ComAtprotoRepoStrongRef from '../repo/strongRef' @@ -48,7 +48,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/identity/resolveHandle.ts b/packages/pds/src/lexicon/types/com/atproto/identity/resolveHandle.ts index ef90e99bb30..05019df6166 100644 --- a/packages/pds/src/lexicon/types/com/atproto/identity/resolveHandle.ts +++ b/packages/pds/src/lexicon/types/com/atproto/identity/resolveHandle.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The handle to resolve. */ @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/identity/updateHandle.ts b/packages/pds/src/lexicon/types/com/atproto/identity/updateHandle.ts index 1f639c344e9..6782a68ed54 100644 --- a/packages/pds/src/lexicon/types/com/atproto/identity/updateHandle.ts +++ b/packages/pds/src/lexicon/types/com/atproto/identity/updateHandle.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/label/queryLabels.ts b/packages/pds/src/lexicon/types/com/atproto/label/queryLabels.ts index 1d7f8a4def5..0c9d55a6961 100644 --- a/packages/pds/src/lexicon/types/com/atproto/label/queryLabels.ts +++ b/packages/pds/src/lexicon/types/com/atproto/label/queryLabels.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoLabelDefs from './defs' export interface QueryParams { @@ -39,7 +39,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/moderation/createReport.ts b/packages/pds/src/lexicon/types/com/atproto/moderation/createReport.ts index 96aaf4a9c29..c1335eb3d1f 100644 --- a/packages/pds/src/lexicon/types/com/atproto/moderation/createReport.ts +++ b/packages/pds/src/lexicon/types/com/atproto/moderation/createReport.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoModerationDefs from './defs' import * as ComAtprotoAdminDefs from '../admin/defs' import * as ComAtprotoRepoStrongRef from '../repo/strongRef' @@ -52,7 +52,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/applyWrites.ts b/packages/pds/src/lexicon/types/com/atproto/repo/applyWrites.ts index 61d1e7c28e4..7bdb6dc2ed1 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/applyWrites.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/applyWrites.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/createRecord.ts b/packages/pds/src/lexicon/types/com/atproto/repo/createRecord.ts index df8c5d9e600..666b91c828d 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/createRecord.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/createRecord.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -49,7 +49,7 @@ export interface HandlerError { error?: 'InvalidSwap' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/deleteRecord.ts b/packages/pds/src/lexicon/types/com/atproto/repo/deleteRecord.ts index f45118a3769..65ee32d213d 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/deleteRecord.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/deleteRecord.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/describeRepo.ts b/packages/pds/src/lexicon/types/com/atproto/repo/describeRepo.ts index 7b8a2b995eb..38b9c01ef1c 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/describeRepo.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/describeRepo.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The handle or DID of the repo. */ @@ -37,7 +37,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/getRecord.ts b/packages/pds/src/lexicon/types/com/atproto/repo/getRecord.ts index 35c9b4b7166..345dde29a53 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/getRecord.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/getRecord.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The handle or DID of the repo. */ @@ -41,7 +41,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/listRecords.ts b/packages/pds/src/lexicon/types/com/atproto/repo/listRecords.ts index a6cf6abd1f3..f46f6eb0f7f 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/listRecords.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/listRecords.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The handle or DID of the repo. */ @@ -45,7 +45,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/putRecord.ts b/packages/pds/src/lexicon/types/com/atproto/repo/putRecord.ts index f10f773c1c4..de93e2e9cf7 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/putRecord.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/putRecord.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -51,7 +51,7 @@ export interface HandlerError { error?: 'InvalidSwap' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/uploadBlob.ts b/packages/pds/src/lexicon/types/com/atproto/repo/uploadBlob.ts index ad6002df925..febbbff9d16 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/uploadBlob.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/uploadBlob.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -34,7 +34,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/server/confirmEmail.ts b/packages/pds/src/lexicon/types/com/atproto/server/confirmEmail.ts index ffaeeb8fe75..b667a04b996 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/confirmEmail.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/confirmEmail.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts b/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts index bbf2c009bf5..bceb61546cf 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -56,7 +56,7 @@ export interface HandlerError { | 'IncompatibleDidDoc' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/server/createAppPassword.ts b/packages/pds/src/lexicon/types/com/atproto/server/createAppPassword.ts index 8e4a0a519e0..474846546fe 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/createAppPassword.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/createAppPassword.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -34,7 +34,7 @@ export interface HandlerError { error?: 'AccountTakedown' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/server/createInviteCode.ts b/packages/pds/src/lexicon/types/com/atproto/server/createInviteCode.ts index acfac56ba76..9cfeacc7e28 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/createInviteCode.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/createInviteCode.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -37,7 +37,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/server/createInviteCodes.ts b/packages/pds/src/lexicon/types/com/atproto/server/createInviteCodes.ts index 5887d77fada..eb6cd2bb1b1 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/createInviteCodes.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/createInviteCodes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -38,7 +38,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/server/createSession.ts b/packages/pds/src/lexicon/types/com/atproto/server/createSession.ts index 2cd448703a6..3952959fe5e 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/createSession.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/createSession.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -45,7 +45,7 @@ export interface HandlerError { error?: 'AccountTakedown' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/server/deleteAccount.ts b/packages/pds/src/lexicon/types/com/atproto/server/deleteAccount.ts index 37ddbba13e0..4fcec360a11 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/deleteAccount.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/deleteAccount.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/server/deleteSession.ts b/packages/pds/src/lexicon/types/com/atproto/server/deleteSession.ts index e4244870425..82672f1d1c7 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/deleteSession.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/deleteSession.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/server/describeServer.ts b/packages/pds/src/lexicon/types/com/atproto/server/describeServer.ts index bb574dba9ff..47be5b598b0 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/describeServer.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/describeServer.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -33,7 +33,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts b/packages/pds/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts index e387a5e38e4..2dc551a477c 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoServerDefs from './defs' export interface QueryParams { @@ -35,7 +35,7 @@ export interface HandlerError { error?: 'DuplicateCreate' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/server/getSession.ts b/packages/pds/src/lexicon/types/com/atproto/server/getSession.ts index 4f95acf523d..5a8c40b947e 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/getSession.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/getSession.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -34,7 +34,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/server/listAppPasswords.ts b/packages/pds/src/lexicon/types/com/atproto/server/listAppPasswords.ts index ebd74da9d39..241418d932d 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/listAppPasswords.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/listAppPasswords.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -31,7 +31,7 @@ export interface HandlerError { error?: 'AccountTakedown' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/server/refreshSession.ts b/packages/pds/src/lexicon/types/com/atproto/server/refreshSession.ts index 35874f78a69..3adeb7fc20e 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/refreshSession.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/refreshSession.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -35,7 +35,7 @@ export interface HandlerError { error?: 'AccountTakedown' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/server/requestAccountDelete.ts b/packages/pds/src/lexicon/types/com/atproto/server/requestAccountDelete.ts index e4244870425..82672f1d1c7 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/requestAccountDelete.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/requestAccountDelete.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/server/requestEmailConfirmation.ts b/packages/pds/src/lexicon/types/com/atproto/server/requestEmailConfirmation.ts index e4244870425..82672f1d1c7 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/requestEmailConfirmation.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/requestEmailConfirmation.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/server/requestEmailUpdate.ts b/packages/pds/src/lexicon/types/com/atproto/server/requestEmailUpdate.ts index 6876d44ca46..24dce3e12af 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/requestEmailUpdate.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/requestEmailUpdate.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -30,7 +30,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/server/requestPasswordReset.ts b/packages/pds/src/lexicon/types/com/atproto/server/requestPasswordReset.ts index 47fb4bb62f3..d0f3f2ad769 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/requestPasswordReset.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/requestPasswordReset.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/server/reserveSigningKey.ts b/packages/pds/src/lexicon/types/com/atproto/server/reserveSigningKey.ts index ad5a5a8758c..0de1220f4b5 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/reserveSigningKey.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/reserveSigningKey.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -38,7 +38,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/server/resetPassword.ts b/packages/pds/src/lexicon/types/com/atproto/server/resetPassword.ts index 9e6ece3e4c4..38f63382cf0 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/resetPassword.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/resetPassword.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/server/revokeAppPassword.ts b/packages/pds/src/lexicon/types/com/atproto/server/revokeAppPassword.ts index 4627f68eaa2..769ad6aa521 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/revokeAppPassword.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/revokeAppPassword.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/server/updateEmail.ts b/packages/pds/src/lexicon/types/com/atproto/server/updateEmail.ts index c88bd3021b2..5473d7571e9 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/updateEmail.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/updateEmail.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/getBlob.ts b/packages/pds/src/lexicon/types/com/atproto/sync/getBlob.ts index 60750902472..b3980fca500 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/getBlob.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/getBlob.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -30,7 +30,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/getBlocks.ts b/packages/pds/src/lexicon/types/com/atproto/sync/getBlocks.ts index e73410efb41..f1b8ebe5db1 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/getBlocks.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/getBlocks.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -29,7 +29,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/getCheckout.ts b/packages/pds/src/lexicon/types/com/atproto/sync/getCheckout.ts index 63a657e56b9..51856b9088d 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/getCheckout.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/getCheckout.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -28,7 +28,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/getHead.ts b/packages/pds/src/lexicon/types/com/atproto/sync/getHead.ts index 586ae1a4189..adedd4cf211 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/getHead.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/getHead.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -34,7 +34,7 @@ export interface HandlerError { error?: 'HeadNotFound' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/getLatestCommit.ts b/packages/pds/src/lexicon/types/com/atproto/sync/getLatestCommit.ts index 9b91e878724..bbae68bbe76 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/getLatestCommit.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/getLatestCommit.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -35,7 +35,7 @@ export interface HandlerError { error?: 'RepoNotFound' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/getRecord.ts b/packages/pds/src/lexicon/types/com/atproto/sync/getRecord.ts index 297f0ac7794..e27878ff5e6 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/getRecord.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/getRecord.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -32,7 +32,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/getRepo.ts b/packages/pds/src/lexicon/types/com/atproto/sync/getRepo.ts index 495d31a1a22..e0ad53ded7c 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/getRepo.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/getRepo.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -30,7 +30,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/listBlobs.ts b/packages/pds/src/lexicon/types/com/atproto/sync/listBlobs.ts index b397bb3b3df..67a66577809 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/listBlobs.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/listBlobs.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -38,7 +38,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/listRepos.ts b/packages/pds/src/lexicon/types/com/atproto/sync/listRepos.ts index 783a8e314c2..12532860895 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/listRepos.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/listRepos.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { limit: number @@ -34,7 +34,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts b/packages/pds/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts index 3d310c1139a..f9498e6691d 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/requestCrawl.ts b/packages/pds/src/lexicon/types/com/atproto/sync/requestCrawl.ts index 87ef20d7297..2859e28fe69 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/requestCrawl.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/requestCrawl.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/temp/checkSignupQueue.ts b/packages/pds/src/lexicon/types/com/atproto/temp/checkSignupQueue.ts index d2a431430a8..9486bce2b2b 100644 --- a/packages/pds/src/lexicon/types/com/atproto/temp/checkSignupQueue.ts +++ b/packages/pds/src/lexicon/types/com/atproto/temp/checkSignupQueue.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -32,7 +32,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/temp/fetchLabels.ts b/packages/pds/src/lexicon/types/com/atproto/temp/fetchLabels.ts index 39341fd3a0e..0fbdeed1196 100644 --- a/packages/pds/src/lexicon/types/com/atproto/temp/fetchLabels.ts +++ b/packages/pds/src/lexicon/types/com/atproto/temp/fetchLabels.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as ComAtprotoLabelDefs from '../label/defs' export interface QueryParams { @@ -34,7 +34,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/temp/importRepo.ts b/packages/pds/src/lexicon/types/com/atproto/temp/importRepo.ts index d88361d9856..44bce41481c 100644 --- a/packages/pds/src/lexicon/types/com/atproto/temp/importRepo.ts +++ b/packages/pds/src/lexicon/types/com/atproto/temp/importRepo.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ @@ -32,7 +32,7 @@ export interface HandlerError { message?: string } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/lexicon/types/com/atproto/temp/pushBlob.ts b/packages/pds/src/lexicon/types/com/atproto/temp/pushBlob.ts index 97e890dbb14..d18a60b598f 100644 --- a/packages/pds/src/lexicon/types/com/atproto/temp/pushBlob.ts +++ b/packages/pds/src/lexicon/types/com/atproto/temp/pushBlob.ts @@ -7,7 +7,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ diff --git a/packages/pds/src/lexicon/types/com/atproto/temp/requestPhoneVerification.ts b/packages/pds/src/lexicon/types/com/atproto/temp/requestPhoneVerification.ts index 5a295f701eb..c977500fc33 100644 --- a/packages/pds/src/lexicon/types/com/atproto/temp/requestPhoneVerification.ts +++ b/packages/pds/src/lexicon/types/com/atproto/temp/requestPhoneVerification.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} diff --git a/packages/pds/src/lexicon/types/com/atproto/temp/transferAccount.ts b/packages/pds/src/lexicon/types/com/atproto/temp/transferAccount.ts index 86c1d750e07..565150caba2 100644 --- a/packages/pds/src/lexicon/types/com/atproto/temp/transferAccount.ts +++ b/packages/pds/src/lexicon/types/com/atproto/temp/transferAccount.ts @@ -6,7 +6,7 @@ import { ValidationResult, BlobRef } from '@atproto/lexicon' import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' -import { HandlerAuth } from '@atproto/xrpc-server' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} @@ -49,7 +49,7 @@ export interface HandlerError { | 'IncompatibleDidDoc' } -export type HandlerOutput = HandlerError | HandlerSuccess +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough export type HandlerReqCtx = { auth: HA params: QueryParams diff --git a/packages/pds/src/pipethrough.ts b/packages/pds/src/pipethrough.ts new file mode 100644 index 00000000000..a4c11856502 --- /dev/null +++ b/packages/pds/src/pipethrough.ts @@ -0,0 +1,93 @@ +import * as ui8 from 'uint8arrays' +import { jsonToLex } from '@atproto/lexicon' +import { HandlerPipeThrough } from '@atproto/xrpc-server' +import { CallOptions, ResponseType, XRPCError } from '@atproto/xrpc' +import { lexicons } from './lexicon/lexicons' +import { httpLogger } from './logger' +import { noUndefinedVals } from '@atproto/common' + +export const pipethrough = async ( + serviceUrl: string, + nsid: string, + params: Record, + opts?: CallOptions, +): Promise => { + const url = constructUrl(serviceUrl, nsid, params) + let res: Response + let buffer: ArrayBuffer + try { + res = await fetch(url, opts) + buffer = await res.arrayBuffer() + } catch (err) { + httpLogger.warn({ err }, 'pipethrough network error') + throw new XRPCError(ResponseType.UpstreamFailure) + } + if (res.status !== ResponseType.Success) { + const ui8Buffer = new Uint8Array(buffer) + const errInfo = safeParseJson(ui8.toString(ui8Buffer, 'utf8')) + throw new XRPCError( + res.status, + safeString(errInfo?.['error']), + safeString(errInfo?.['message']), + simpleHeaders(res.headers), + ) + } + const encoding = res.headers.get('content-type') ?? 'application/json' + const repoRevHeader = res.headers.get('atproto-repo-rev') + const contentLanguage = res.headers.get('content-language') + const headers = noUndefinedVals({ + ['atproto-repo-rev']: repoRevHeader ?? undefined, + ['content-language']: contentLanguage ?? undefined, + }) + return { encoding, buffer, headers } +} + +export const constructUrl = ( + serviceUrl: string, + nsid: string, + params?: Record, +): string => { + const uri = new URL(serviceUrl) + uri.pathname = `/xrpc/${nsid}` + + for (const [key, value] of Object.entries(params ?? {})) { + if (value === undefined) { + continue + } else if (Array.isArray(value)) { + for (const item of value) { + uri.searchParams.append(key, String(item)) + } + } else { + uri.searchParams.set(key, String(value)) + } + } + + return uri.toString() +} + +export const parseRes = (nsid: string, res: HandlerPipeThrough): T => { + const buffer = new Uint8Array(res.buffer) + const json = safeParseJson(ui8.toString(buffer, 'utf8')) + const lex = json && jsonToLex(json) + return lexicons.assertValidXrpcOutput(nsid, lex) as T +} + +const safeString = (str: string): string | undefined => { + return typeof str === 'string' ? str : undefined +} + +const safeParseJson = (json: string): unknown => { + try { + return JSON.parse(json) + } catch { + return null + } +} + +const simpleHeaders = (headers: Headers): Record => { + const result = {} + for (const [key, val] of headers) { + result[key] = val + } + return result +} diff --git a/packages/pds/src/read-after-write/types.ts b/packages/pds/src/read-after-write/types.ts index 170256e566e..cfdd6ff7fe3 100644 --- a/packages/pds/src/read-after-write/types.ts +++ b/packages/pds/src/read-after-write/types.ts @@ -6,6 +6,7 @@ import { Record as ProfileRecord } from '../lexicon/types/app/bsky/actor/profile import { LocalViewer } from './viewer' export type LocalRecords = { + count: number profile: RecordDescript | null posts: RecordDescript[] } diff --git a/packages/pds/src/read-after-write/util.ts b/packages/pds/src/read-after-write/util.ts index db8770a32b6..cb659a84121 100644 --- a/packages/pds/src/read-after-write/util.ts +++ b/packages/pds/src/read-after-write/util.ts @@ -1,10 +1,15 @@ import { Headers } from '@atproto/xrpc' import { readStickyLogger as log } from '../logger' import AppContext from '../context' -import { ApiRes, HandlerResponse, LocalRecords, MungeFn } from './types' +import { HandlerResponse, LocalRecords, MungeFn } from './types' +import { getRecordsSinceRev } from './viewer' +import { HandlerPipeThrough } from '@atproto/xrpc-server' +import { parseRes } from '../pipethrough' + +const REPO_REV_HEADER = 'atproto-repo-rev' export const getRepoRev = (headers: Headers): string | undefined => { - return headers['atproto-repo-rev'] + return headers[REPO_REV_HEADER] } export const getLocalLag = (local: LocalRecords): number | undefined => { @@ -20,45 +25,51 @@ export const getLocalLag = (local: LocalRecords): number | undefined => { export const handleReadAfterWrite = async ( ctx: AppContext, + nsid: string, requester: string, - res: ApiRes, + res: HandlerPipeThrough, munge: MungeFn, -): Promise> => { - let body: T - let lag: number | undefined = undefined +): Promise | HandlerPipeThrough> => { try { - const withLocal = await readAfterWriteInternal(ctx, requester, res, munge) - body = withLocal.data - lag = withLocal.lag + return await readAfterWriteInternal(ctx, nsid, requester, res, munge) } catch (err) { - body = res.data log.warn({ err, requester }, 'error in read after write munge') - } - return { - encoding: 'application/json', - body, - headers: - lag !== undefined - ? { - 'Atproto-Upstream-Lag': lag.toString(10), - } - : undefined, + return res } } export const readAfterWriteInternal = async ( ctx: AppContext, + nsid: string, requester: string, - res: ApiRes, + res: HandlerPipeThrough, munge: MungeFn, -): Promise<{ data: T; lag?: number }> => { - const rev = getRepoRev(res.headers) - if (!rev) return { data: res.data } - const keypair = await ctx.actorStore.keypair(requester) +): Promise | HandlerPipeThrough> => { + const rev = getRepoRev(res.headers ?? {}) + if (!rev) return res return ctx.actorStore.read(requester, async (store) => { + const local = await getRecordsSinceRev(store, rev) + if (local.count === 0) { + return res + } + const keypair = await ctx.actorStore.keypair(requester) const localViewer = ctx.localViewer(store, keypair) - const local = await localViewer.getRecordsSinceRev(rev) - const data = await munge(localViewer, res.data, local, requester) - return { data, lag: getLocalLag(local) } + const parsedRes = parseRes(nsid, res) + const data = await munge(localViewer, parsedRes, local, requester) + return formatMungedResponse(data, getLocalLag(local)) }) } + +export const formatMungedResponse = ( + body: T, + lag?: number, +): HandlerResponse => ({ + encoding: 'application/json', + body, + headers: + lag !== undefined + ? { + 'Atproto-Upstream-Lag': lag.toString(10), + } + : undefined, +}) diff --git a/packages/pds/src/read-after-write/viewer.ts b/packages/pds/src/read-after-write/viewer.ts index a3da0e5eb88..6777b951cdf 100644 --- a/packages/pds/src/read-after-write/viewer.ts +++ b/packages/pds/src/read-after-write/viewer.ts @@ -97,51 +97,7 @@ export class LocalViewer { } async getRecordsSinceRev(rev: string): Promise { - const res = await this.actorStore.db.db - .selectFrom('record') - .innerJoin('repo_block', 'repo_block.cid', 'record.cid') - .select([ - 'repo_block.content', - 'uri', - 'repo_block.cid', - 'record.indexedAt', - ]) - .where('record.repoRev', '>', rev) - .limit(10) - .orderBy('record.repoRev', 'asc') - .execute() - // sanity check to ensure that the clock received is not before _all_ local records (for instance in case of account migration) - if (res.length > 0) { - const sanityCheckRes = await this.actorStore.db.db - .selectFrom('record') - .selectAll() - .where('record.repoRev', '<=', rev) - .limit(1) - .executeTakeFirst() - if (!sanityCheckRes) { - return { profile: null, posts: [] } - } - } - return res.reduce( - (acc, cur) => { - const descript = { - uri: new AtUri(cur.uri), - cid: CID.parse(cur.cid), - indexedAt: cur.indexedAt, - record: cborToLexRecord(cur.content), - } - if ( - descript.uri.collection === ids.AppBskyActorProfile && - descript.uri.rkey === 'self' - ) { - acc.profile = descript as RecordDescript - } else if (descript.uri.collection === ids.AppBskyFeedPost) { - acc.posts.push(descript as RecordDescript) - } - return acc - }, - { profile: null, posts: [] } as LocalRecords, - ) + return getRecordsSinceRev(this.actorStore, rev) } async getProfileBasic(): Promise { @@ -364,3 +320,50 @@ export class LocalViewer { } } } + +export const getRecordsSinceRev = async ( + actorStore: ActorStoreReader, + rev: string, +): Promise => { + const res = await actorStore.db.db + .selectFrom('record') + .innerJoin('repo_block', 'repo_block.cid', 'record.cid') + .select(['repo_block.content', 'uri', 'repo_block.cid', 'record.indexedAt']) + .where('record.repoRev', '>', rev) + .limit(10) + .orderBy('record.repoRev', 'asc') + .execute() + // sanity check to ensure that the clock received is not before _all_ local records (for instance in case of account migration) + if (res.length > 0) { + const sanityCheckRes = await actorStore.db.db + .selectFrom('record') + .selectAll() + .where('record.repoRev', '<=', rev) + .limit(1) + .executeTakeFirst() + if (!sanityCheckRes) { + return { count: 0, profile: null, posts: [] } + } + } + return res.reduce( + (acc, cur) => { + const descript = { + uri: new AtUri(cur.uri), + cid: CID.parse(cur.cid), + indexedAt: cur.indexedAt, + record: cborToLexRecord(cur.content), + } + if ( + descript.uri.collection === ids.AppBskyActorProfile && + descript.uri.rkey === 'self' + ) { + acc.profile = descript as RecordDescript + } else if (descript.uri.collection === ids.AppBskyFeedPost) { + acc.posts.push(descript as RecordDescript) + } + acc.count++ + return acc + }, + { count: 0, profile: null, posts: [] } as LocalRecords, + ) +} diff --git a/packages/xrpc-server/src/server.ts b/packages/xrpc-server/src/server.ts index 9a666488c1c..75621136995 100644 --- a/packages/xrpc-server/src/server.ts +++ b/packages/xrpc-server/src/server.ts @@ -36,6 +36,8 @@ import { RateLimiterConsume, isShared, RateLimitExceededError, + HandlerPipeThrough, + handlerPipeThrough, } from './types' import { decodeQueryParams, @@ -263,6 +265,20 @@ export class Server { throw XRPCError.fromError(outputUnvalidated) } + if (outputUnvalidated && isHandlerPipeThrough(outputUnvalidated)) { + // set headers + if (outputUnvalidated?.headers) { + Object.entries(outputUnvalidated.headers).forEach(([name, val]) => { + res.header(name, val) + }) + } + res + .header('Content-Type', outputUnvalidated.encoding) + .status(200) + .send(Buffer.from(outputUnvalidated.buffer)) + return + } + if (!outputUnvalidated || isHandlerSuccess(outputUnvalidated)) { // validate response const output = validateResOutput(outputUnvalidated) @@ -448,6 +464,26 @@ function isHandlerSuccess(v: HandlerOutput): v is HandlerSuccess { return handlerSuccess.safeParse(v).success } +function isHandlerPipeThrough(v: HandlerOutput): v is HandlerPipeThrough { + if (v === null || typeof v !== 'object') { + return false + } + if (!isString(v['encoding']) || !(v['buffer'] instanceof ArrayBuffer)) { + return false + } + if (v['headers'] !== undefined) { + if (v['headers'] === null || typeof v['headers'] !== 'object') { + return false + } + if (!Object.values(v['headers']).every(isString)) { + return false + } + } + return true +} + +const isString = (val: unknown): val is string => typeof val === 'string' + const kRequestLocals = Symbol('requestLocals') function createLocalsMiddleware(nsid: string): RequestHandler { diff --git a/packages/xrpc-server/src/types.ts b/packages/xrpc-server/src/types.ts index cba891a0d63..5d406d77a11 100644 --- a/packages/xrpc-server/src/types.ts +++ b/packages/xrpc-server/src/types.ts @@ -46,6 +46,13 @@ export const handlerSuccess = zod.object({ }) export type HandlerSuccess = zod.infer +export const handlerPipeThrough = zod.object({ + encoding: zod.string(), + buffer: zod.instanceof(ArrayBuffer), + headers: zod.record(zod.string()).optional(), +}) +export type HandlerPipeThrough = zod.infer + export const handlerError = zod.object({ status: zod.number(), error: zod.string().optional(), @@ -53,7 +60,7 @@ export const handlerError = zod.object({ }) export type HandlerError = zod.infer -export type HandlerOutput = HandlerSuccess | HandlerError +export type HandlerOutput = HandlerSuccess | HandlerPipeThrough | HandlerError export type XRPCReqContext = { auth: HandlerAuth | undefined From d0be052e12b4bf8f8ff69027b5ac2c0f358429a7 Mon Sep 17 00:00:00 2001 From: devin ivy Date: Fri, 9 Feb 2024 19:05:29 -0500 Subject: [PATCH 005/103] Support node v20 on pds distribution (#2157) * upgrade node to v20, better-sqlite3 to v9 * fix tests, deps for node v20 * build * add io_uring setting to pds dockerfile --- .../workflows/build-and-push-pds-ghcr.yaml | 2 +- packages/dev-env/package.json | 2 +- packages/pds/package.json | 4 +- packages/pds/tests/crud.test.ts | 2 +- packages/pds/tests/entryway.test.ts | 4 + packages/pds/tests/transfer-repo.test.ts | 4 + pnpm-lock.yaml | 231 ++++++++++++------ services/pds/Dockerfile | 6 +- 8 files changed, 177 insertions(+), 78 deletions(-) diff --git a/.github/workflows/build-and-push-pds-ghcr.yaml b/.github/workflows/build-and-push-pds-ghcr.yaml index 013388bd1f4..28932c5959b 100644 --- a/.github/workflows/build-and-push-pds-ghcr.yaml +++ b/.github/workflows/build-and-push-pds-ghcr.yaml @@ -3,7 +3,7 @@ on: push: branches: - main - - pds-pipethrough + - pds-node-v20 env: REGISTRY: ghcr.io USERNAME: ${{ github.actor }} diff --git a/packages/dev-env/package.json b/packages/dev-env/package.json index b38df90c2b9..87cfc87882a 100644 --- a/packages/dev-env/package.json +++ b/packages/dev-env/package.json @@ -39,7 +39,7 @@ "@did-plc/lib": "^0.0.1", "@did-plc/server": "^0.0.1", "axios": "^0.27.2", - "better-sqlite3": "^7.6.2", + "better-sqlite3": "^9.4.0", "chalk": "^5.0.1", "dotenv": "^16.0.3", "express": "^4.18.2", diff --git a/packages/pds/package.json b/packages/pds/package.json index 1c5cc838628..4bc30f5fade 100644 --- a/packages/pds/package.json +++ b/packages/pds/package.json @@ -44,7 +44,7 @@ "@atproto/xrpc": "workspace:^", "@atproto/xrpc-server": "workspace:^", "@did-plc/lib": "^0.0.4", - "better-sqlite3": "^7.6.2", + "better-sqlite3": "^9.4.0", "bytes": "^3.1.2", "compression": "^1.7.4", "cors": "^2.8.5", @@ -77,7 +77,7 @@ "@atproto/bsky": "workspace:^", "@atproto/dev-env": "workspace:^", "@atproto/lex-cli": "workspace:^", - "@atproto/pds-entryway": "npm:@atproto/pds@0.3.0-entryway.2", + "@atproto/pds-entryway": "npm:@atproto/pds@0.3.0-entryway.3", "@did-plc/server": "^0.0.1", "@types/cors": "^2.8.12", "@types/disposable-email": "^0.2.0", diff --git a/packages/pds/tests/crud.test.ts b/packages/pds/tests/crud.test.ts index e675119dca2..d70da099ce3 100644 --- a/packages/pds/tests/crud.test.ts +++ b/packages/pds/tests/crud.test.ts @@ -937,7 +937,7 @@ describe('crud operations', () => { record: { text: 'x', createdAt: new Date().toISOString(), - deepObject: createDeepObject(4000), + deepObject: createDeepObject(3000), }, }), ) diff --git a/packages/pds/tests/entryway.test.ts b/packages/pds/tests/entryway.test.ts index dc3ba5c7e35..d27c49c585c 100644 --- a/packages/pds/tests/entryway.test.ts +++ b/packages/pds/tests/entryway.test.ts @@ -176,6 +176,8 @@ const createEntryway = async ( bskyAppViewCdnUrlPattern: 'http://cdn.appview.com/%s/%s/%s', jwtSecret: randomStr(8, 'base32'), repoSigningKeyK256PrivateKeyHex: await getPrivateHex(signingKey), + modServiceUrl: 'https://mod.invalid', + modServiceDid: 'did:example:invalid', ...config, } const cfg = pdsEntryway.envToCfg(env) @@ -183,6 +185,8 @@ const createEntryway = async ( const server = await pdsEntryway.PDS.create(cfg, secrets) await server.ctx.db.migrateToLatestOrThrow() await server.start() + // @TODO temp hack because entryway teardown calls signupActivator.run() by mistake + server.ctx.signupActivator.run = server.ctx.signupActivator.destroy return server } diff --git a/packages/pds/tests/transfer-repo.test.ts b/packages/pds/tests/transfer-repo.test.ts index 53393fa89d1..48309b821ae 100644 --- a/packages/pds/tests/transfer-repo.test.ts +++ b/packages/pds/tests/transfer-repo.test.ts @@ -198,6 +198,8 @@ const createEntryway = async ( bskyAppViewCdnUrlPattern: 'http://cdn.appview.com/%s/%s/%s', jwtSecret: randomStr(8, 'base32'), repoSigningKeyK256PrivateKeyHex: await getPrivateHex(signingKey), + modServiceUrl: 'https://mod.invalid', + modServiceDid: 'did:example:invalid', ...config, } const cfg = pdsEntryway.envToCfg(env) @@ -205,6 +207,8 @@ const createEntryway = async ( const server = await pdsEntryway.PDS.create(cfg, secrets) await server.ctx.db.migrateToLatestOrThrow() await server.start() + // @TODO temp hack because entryway teardown calls signupActivator.run() by mistake + server.ctx.signupActivator.run = server.ctx.signupActivator.destroy return server } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c8d6db85650..f13b09703e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -457,8 +457,8 @@ importers: specifier: ^0.27.2 version: 0.27.2 better-sqlite3: - specifier: ^7.6.2 - version: 7.6.2 + specifier: ^9.4.0 + version: 9.4.0 chalk: specifier: ^5.0.1 version: 5.1.1 @@ -691,8 +691,8 @@ importers: specifier: ^0.0.4 version: 0.0.4 better-sqlite3: - specifier: ^7.6.2 - version: 7.6.2 + specifier: ^9.4.0 + version: 9.4.0 bytes: specifier: ^3.1.2 version: 3.1.2 @@ -782,8 +782,8 @@ importers: specifier: workspace:^ version: link:../lex-cli '@atproto/pds-entryway': - specifier: npm:@atproto/pds@0.3.0-entryway.2 - version: /@atproto/pds@0.3.0-entryway.2 + specifier: npm:@atproto/pds@0.3.0-entryway.3 + version: /@atproto/pds@0.3.0-entryway.3 '@did-plc/server': specifier: ^0.0.1 version: 0.0.1 @@ -982,7 +982,7 @@ importers: version: 4.20.0 opentelemetry-plugin-better-sqlite3: specifier: ^1.1.0 - version: 1.1.0(better-sqlite3@7.6.2) + version: 1.1.0(better-sqlite3@9.4.0) packages: @@ -1024,9 +1024,8 @@ packages: one-webcrypto: 1.0.3 uint8arrays: 3.0.0 - /@atproto/pds@0.3.0-entryway.2: - resolution: {integrity: sha512-nj3cOgPBiX0PLMG8Wn6Vy9mpRa891nGDXiOURoeSzQPSJMkWlk/4SlfYEFSrGSHlBnkUNd1fKE3NsJMMQJ/Utg==} - hasBin: true + /@atproto/pds@0.3.0-entryway.3: + resolution: {integrity: sha512-hc/KcBgFjSfrnNgrc9vx/QaCov79cXbRRQEWrAt/rdMLhcFEGCydN7ukddjOiLkLdhDqywSHrLOEbqg+MReQXw==} dependencies: '@atproto/api': link:packages/api '@atproto/aws': link:packages/aws @@ -1038,8 +1037,11 @@ packages: '@atproto/syntax': link:packages/syntax '@atproto/xrpc': link:packages/xrpc '@atproto/xrpc-server': link:packages/xrpc-server + '@bufbuild/protobuf': 1.6.0 + '@connectrpc/connect': 1.3.0(@bufbuild/protobuf@1.6.0) + '@connectrpc/connect-node': 1.3.0(@bufbuild/protobuf@1.6.0)(@connectrpc/connect@1.3.0) '@did-plc/lib': 0.0.1 - better-sqlite3: 7.6.2 + better-sqlite3: 9.4.0 bytes: 3.1.2 compression: 1.7.4 cors: 2.8.5 @@ -1062,7 +1064,9 @@ packages: pg: 8.10.0 pino: 8.15.0 pino-http: 8.4.0 - sharp: 0.31.3 + rate-limiter-flexible: 2.4.1 + sharp: 0.32.6 + twilio: 4.21.0 typed-emitter: 2.1.0 uint8arrays: 3.0.0 zod: 3.21.4 @@ -4635,48 +4639,48 @@ packages: - supports-color dev: true - /@cbor-extract/cbor-extract-darwin-arm64@2.1.1: - resolution: {integrity: sha512-blVBy5MXz6m36Vx0DfLd7PChOQKEs8lK2bD1WJn/vVgG4FXZiZmZb2GECHFvVPA5T7OnODd9xZiL3nMCv6QUhA==} + /@cbor-extract/cbor-extract-darwin-arm64@2.2.0: + resolution: {integrity: sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==} cpu: [arm64] os: [darwin] requiresBuild: true dev: false optional: true - /@cbor-extract/cbor-extract-darwin-x64@2.1.1: - resolution: {integrity: sha512-h6KFOzqk8jXTvkOftyRIWGrd7sKQzQv2jVdTL9nKSf3D2drCvQB/LHUxAOpPXo3pv2clDtKs3xnHalpEh3rDsw==} + /@cbor-extract/cbor-extract-darwin-x64@2.2.0: + resolution: {integrity: sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==} cpu: [x64] os: [darwin] requiresBuild: true dev: false optional: true - /@cbor-extract/cbor-extract-linux-arm64@2.1.1: - resolution: {integrity: sha512-SxAaRcYf8S0QHaMc7gvRSiTSr7nUYMqbUdErBEu+HYA4Q6UNydx1VwFE68hGcp1qvxcy9yT5U7gA+a5XikfwSQ==} + /@cbor-extract/cbor-extract-linux-arm64@2.2.0: + resolution: {integrity: sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==} cpu: [arm64] os: [linux] requiresBuild: true dev: false optional: true - /@cbor-extract/cbor-extract-linux-arm@2.1.1: - resolution: {integrity: sha512-ds0uikdcIGUjPyraV4oJqyVE5gl/qYBpa/Wnh6l6xLE2lj/hwnjT2XcZCChdXwW/YFZ1LUHs6waoYN8PmK0nKQ==} + /@cbor-extract/cbor-extract-linux-arm@2.2.0: + resolution: {integrity: sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==} cpu: [arm] os: [linux] requiresBuild: true dev: false optional: true - /@cbor-extract/cbor-extract-linux-x64@2.1.1: - resolution: {integrity: sha512-GVK+8fNIE9lJQHAlhOROYiI0Yd4bAZ4u++C2ZjlkS3YmO6hi+FUxe6Dqm+OKWTcMpL/l71N6CQAmaRcb4zyJuA==} + /@cbor-extract/cbor-extract-linux-x64@2.2.0: + resolution: {integrity: sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==} cpu: [x64] os: [linux] requiresBuild: true dev: false optional: true - /@cbor-extract/cbor-extract-win32-x64@2.1.1: - resolution: {integrity: sha512-2Niq1C41dCRIDeD8LddiH+mxGlO7HJ612Ll3D/E73ZWBmycued+8ghTr/Ho3CMOWPUEr08XtyBMVXAjqF+TcKw==} + /@cbor-extract/cbor-extract-win32-x64@2.2.0: + resolution: {integrity: sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==} cpu: [x64] os: [win32] requiresBuild: true @@ -4910,7 +4914,6 @@ packages: '@bufbuild/protobuf': 1.6.0 '@connectrpc/connect': 1.3.0(@bufbuild/protobuf@1.6.0) undici: 5.28.2 - dev: false /@connectrpc/connect@1.3.0(@bufbuild/protobuf@1.6.0): resolution: {integrity: sha512-kTeWxJnLLtxKc2ZSDN0rIBgwfP8RwcLknthX4AKlIAmN9ZC4gGnCbwp+3BKcP/WH5c8zGBAWqSY3zeqCM+ah7w==} @@ -5121,7 +5124,6 @@ packages: /@fastify/busboy@2.1.0: resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==} engines: {node: '>=14'} - dev: false /@fastify/deepmerge@1.3.0: resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} @@ -6430,7 +6432,6 @@ packages: /b4a@1.6.4: resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==} - dev: false /babel-eslint@10.1.0(eslint@8.24.0): resolution: {integrity: sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==} @@ -6571,8 +6572,8 @@ packages: is-windows: 1.0.2 dev: true - /better-sqlite3@7.6.2: - resolution: {integrity: sha512-S5zIU1Hink2AH4xPsN0W43T1/AJ5jrPh7Oy07ocuW/AKYYY02GWzz9NH0nbSMn/gw6fDZ5jZ1QsHt1BXAwJ6Lg==} + /better-sqlite3@9.4.0: + resolution: {integrity: sha512-5kynxekMxSjCMiFyUBLHggFcJkCmiZi6fUkiGz/B5GZOvdRWQJD0klqCx5/Y+bm2AKP7I/DHbSFx26AxjruWNg==} requiresBuild: true dependencies: bindings: 1.5.0 @@ -6667,6 +6668,10 @@ packages: node-int64: 0.4.0 dev: true + /buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + dev: true + /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} dev: true @@ -6762,26 +6767,26 @@ packages: resolution: {integrity: sha512-TKiyTVZxJGhsTszLuzb+6vUZSjVOAhClszBr2Ta2k9IwtNBT/4dzmL6aywt0HCgEZlmwJzXJd8yNiob6HgwTRg==} dev: true - /cbor-extract@2.1.1: - resolution: {integrity: sha512-1UX977+L+zOJHsp0mWFG13GLwO6ucKgSmSW6JTl8B9GUvACvHeIVpFqhU92299Z6PfD09aTXDell5p+lp1rUFA==} + /cbor-extract@2.2.0: + resolution: {integrity: sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==} hasBin: true requiresBuild: true dependencies: - node-gyp-build-optional-packages: 5.0.3 + node-gyp-build-optional-packages: 5.1.1 optionalDependencies: - '@cbor-extract/cbor-extract-darwin-arm64': 2.1.1 - '@cbor-extract/cbor-extract-darwin-x64': 2.1.1 - '@cbor-extract/cbor-extract-linux-arm': 2.1.1 - '@cbor-extract/cbor-extract-linux-arm64': 2.1.1 - '@cbor-extract/cbor-extract-linux-x64': 2.1.1 - '@cbor-extract/cbor-extract-win32-x64': 2.1.1 + '@cbor-extract/cbor-extract-darwin-arm64': 2.2.0 + '@cbor-extract/cbor-extract-darwin-x64': 2.2.0 + '@cbor-extract/cbor-extract-linux-arm': 2.2.0 + '@cbor-extract/cbor-extract-linux-arm64': 2.2.0 + '@cbor-extract/cbor-extract-linux-x64': 2.2.0 + '@cbor-extract/cbor-extract-win32-x64': 2.2.0 dev: false optional: true /cbor-x@1.5.1: resolution: {integrity: sha512-/vAkC4tiKCQCm5en4sA+mpKmjwY6Xxp1LO+BgZCNhp+Zow3pomyUHeBOK5EDp0mDaE36jw39l5eLHsoF3M1Lmg==} optionalDependencies: - cbor-extract: 2.1.1 + cbor-extract: 2.2.0 dev: false /cborg@1.10.2: @@ -7058,6 +7063,10 @@ packages: resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} dev: true + /dayjs@1.11.10: + resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} + dev: true + /dc-polyfill@0.1.3: resolution: {integrity: sha512-Wyk5n/5KUj3GfVKV2jtDbtChC/Ff9fjKsBcg4ZtYW1yQe3DXNHcGURvmoxhqQdfOQ9TwyMjnfyv1lyYcOkFkFA==} engines: {node: '>=12.17'} @@ -7307,6 +7316,12 @@ packages: engines: {node: '>=10'} dev: true + /ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + dependencies: + safe-buffer: 5.2.1 + dev: true + /ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -7964,7 +7979,6 @@ packages: /fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - dev: false /fast-glob@3.3.1: resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} @@ -9395,6 +9409,37 @@ packages: graceful-fs: 4.2.11 dev: true + /jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.5.4 + dev: true + + /jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + dev: true + + /jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + dev: true + /key-encoder@2.0.3: resolution: {integrity: sha512-fgBtpAGIr/Fy5/+ZLQZIPPhsZEcbSlYu/Wu96tNDFNSjSACw5lEIOFeaVdQ/iwrb8oxjlWi6wmWdH76hV6GZjg==} dependencies: @@ -9483,9 +9528,33 @@ packages: /lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + /lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + dev: true + /lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + /lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + dev: true + + /lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + dev: true + + /lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + dev: true + + /lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + dev: true + + /lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + dev: true + /lodash.kebabcase@4.1.1: resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} dev: false @@ -9494,6 +9563,10 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + dev: true + /lodash.pick@4.4.0: resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==} dev: false @@ -9824,13 +9897,8 @@ packages: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} dev: false - /node-addon-api@5.1.0: - resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} - dev: true - /node-addon-api@6.1.0: resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} - dev: false /node-cache@5.1.2: resolution: {integrity: sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==} @@ -9851,10 +9919,12 @@ packages: whatwg-url: 5.0.0 dev: true - /node-gyp-build-optional-packages@5.0.3: - resolution: {integrity: sha512-k75jcVzk5wnnc/FMxsf4udAoTEUv2jY3ycfdSd3yWu6Cnd1oee6/CfZJApyscA4FJOmdoixWwiwOyf16RzD5JA==} + /node-gyp-build-optional-packages@5.1.1: + resolution: {integrity: sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==} hasBin: true requiresBuild: true + dependencies: + detect-libc: 2.0.2 dev: false optional: true @@ -9994,7 +10064,7 @@ packages: mimic-fn: 2.1.0 dev: true - /opentelemetry-plugin-better-sqlite3@1.1.0(better-sqlite3@7.6.2): + /opentelemetry-plugin-better-sqlite3@1.1.0(better-sqlite3@9.4.0): resolution: {integrity: sha512-yd+mgaB5W5JxzcQt9TvX1VIrusqtbbeuxSoZ6KQe4Ra0J/Kqkp6kz7dg0VQUU5+cenOWkza6xtvsT0KGXI03HA==} peerDependencies: better-sqlite3: ^7.1.1 || ^8.0.0 || ^9.0.0 @@ -10003,7 +10073,7 @@ packages: '@opentelemetry/core': 1.18.1(@opentelemetry/api@1.7.0) '@opentelemetry/instrumentation': 0.44.0(@opentelemetry/api@1.7.0) '@opentelemetry/semantic-conventions': 1.18.1 - better-sqlite3: 7.6.2 + better-sqlite3: 9.4.0 transitivePeerDependencies: - supports-color dev: false @@ -10479,12 +10549,15 @@ packages: dependencies: side-channel: 1.0.4 + /querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + dev: true + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} /queue-tick@1.0.1: resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} - dev: false /quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} @@ -10500,7 +10573,6 @@ packages: /rate-limiter-flexible@2.4.1: resolution: {integrity: sha512-dgH4T44TzKVO9CLArNto62hJOwlWJMLUjVVr/ii0uUzZXEXthDNr7/yefW5z/1vvHAfycc1tnuiYyNJ8CTRB3g==} - dev: false /raw-body@2.5.1: resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} @@ -10677,6 +10749,10 @@ packages: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} dev: true + /requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + dev: true + /resolve-cwd@3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} @@ -10790,6 +10866,10 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} requiresBuild: true + /scmp@2.1.0: + resolution: {integrity: sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==} + dev: true + /secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} dev: true @@ -10851,21 +10931,6 @@ packages: /setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - /sharp@0.31.3: - resolution: {integrity: sha512-XcR4+FCLBFKw1bdB+GEhnUNXNXvnt0tDo4WsBsraKymuo/IAuPuCBVAL2wIkUw2r/dwFW5Q5+g66Kwl2dgDFVg==} - engines: {node: '>=14.15.0'} - requiresBuild: true - dependencies: - color: 4.2.3 - detect-libc: 2.0.2 - node-addon-api: 5.1.0 - prebuild-install: 7.1.1 - semver: 7.5.4 - simple-get: 4.0.1 - tar-fs: 2.1.1 - tunnel-agent: 0.6.0 - dev: true - /sharp@0.32.6: resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==} engines: {node: '>=14.15.0'} @@ -10879,7 +10944,6 @@ packages: simple-get: 4.0.1 tar-fs: 3.0.4 tunnel-agent: 0.6.0 - dev: false /shebang-command@1.2.0: resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} @@ -11084,7 +11148,6 @@ packages: dependencies: fast-fifo: 1.3.2 queue-tick: 1.0.1 - dev: false /string-length@4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} @@ -11229,7 +11292,6 @@ packages: mkdirp-classic: 0.5.3 pump: 3.0.0 tar-stream: 3.1.6 - dev: false /tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} @@ -11247,7 +11309,6 @@ packages: b4a: 1.6.4 fast-fifo: 1.3.2 streamx: 2.15.5 - dev: false /tar@6.1.15: resolution: {integrity: sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==} @@ -11454,6 +11515,23 @@ packages: dependencies: safe-buffer: 5.2.1 + /twilio@4.21.0: + resolution: {integrity: sha512-+meDbJPOxs6vEysJ7xX7XMn6FLKmZFSeVzMKjzN9NWgDXssp713Kf1ukteZlXhnhd7/NtNiUv5OU17qVgBb/BQ==} + engines: {node: '>=14.0'} + dependencies: + axios: 1.6.2 + dayjs: 1.11.10 + https-proxy-agent: 5.0.1 + jsonwebtoken: 9.0.2 + qs: 6.11.0 + scmp: 2.1.0 + url-parse: 1.5.10 + xmlbuilder: 13.0.2 + transitivePeerDependencies: + - debug + - supports-color + dev: true + /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -11583,7 +11661,6 @@ packages: engines: {node: '>=14.0'} dependencies: '@fastify/busboy': 2.1.0 - dev: false /unicode-canonical-property-names-ecmascript@2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} @@ -11647,6 +11724,13 @@ packages: dependencies: punycode: 2.3.0 + /url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + dev: true + /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -11808,6 +11892,11 @@ packages: utf-8-validate: optional: true + /xmlbuilder@13.0.2: + resolution: {integrity: sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==} + engines: {node: '>=6.0'} + dev: true + /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} diff --git a/services/pds/Dockerfile b/services/pds/Dockerfile index 6d092bb5229..e5f7b057184 100644 --- a/services/pds/Dockerfile +++ b/services/pds/Dockerfile @@ -1,6 +1,6 @@ # @NOTE just a temp fix: alpine3.19 breaks sharp install, see nodejs/docker-node#2009 # see additional reference to this image further down. -FROM node:18-alpine3.18 as build +FROM node:20.11-alpine3.18 as build RUN npm install -g pnpm @@ -37,7 +37,7 @@ RUN pnpm install --prod --shamefully-hoist --frozen-lockfile --prefer-offline > WORKDIR services/pds # Uses assets from build stage to reduce build size -FROM node:18-alpine3.18 +FROM node:20.11-alpine3.18 RUN apk add --update dumb-init @@ -52,6 +52,8 @@ VOLUME /app/data EXPOSE 3000 ENV PDS_PORT=3000 ENV NODE_ENV=production +# potential perf issues w/ io_uring on this version of node +ENV UV_USE_IO_URING=0 # https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#non-root-user USER node From 9579bec720d30e40c995d09772040212c261d6fb Mon Sep 17 00:00:00 2001 From: bnewbold Date: Sun, 11 Feb 2024 16:06:58 -0800 Subject: [PATCH 006/103] update descriptions in Lexicons (#2110) * lexicons: update descriptions in com.atproto.* * lexicons: update descriptions in app.bsky.* * Apply suggestions from code review Thanks Emily! Co-authored-by: Emily Liu * codegen description-only Lexicon updates --------- Co-authored-by: Emily Liu --- lexicons/app/bsky/actor/defs.json | 2 +- lexicons/app/bsky/actor/getPreferences.json | 2 +- lexicons/app/bsky/actor/getProfile.json | 8 +- lexicons/app/bsky/actor/getSuggestions.json | 2 +- lexicons/app/bsky/actor/profile.json | 6 +- lexicons/app/bsky/actor/searchActors.json | 2 +- .../app/bsky/actor/searchActorsTypeahead.json | 2 +- lexicons/app/bsky/embed/external.json | 2 +- lexicons/app/bsky/embed/images.json | 22 +- lexicons/app/bsky/embed/record.json | 7 +- lexicons/app/bsky/embed/recordWithMedia.json | 2 +- lexicons/app/bsky/feed/defs.json | 1 + .../app/bsky/feed/describeFeedGenerator.json | 2 +- lexicons/app/bsky/feed/generator.json | 3 +- lexicons/app/bsky/feed/getActorFeeds.json | 2 +- lexicons/app/bsky/feed/getActorLikes.json | 2 +- lexicons/app/bsky/feed/getAuthorFeed.json | 3 +- lexicons/app/bsky/feed/getFeed.json | 2 +- lexicons/app/bsky/feed/getFeedGenerator.json | 18 +- lexicons/app/bsky/feed/getFeedSkeleton.json | 8 +- lexicons/app/bsky/feed/getLikes.json | 14 +- lexicons/app/bsky/feed/getListFeed.json | 8 +- lexicons/app/bsky/feed/getPostThread.json | 10 +- lexicons/app/bsky/feed/getPosts.json | 3 +- lexicons/app/bsky/feed/getRepostedBy.json | 14 +- lexicons/app/bsky/feed/getSuggestedFeeds.json | 2 +- lexicons/app/bsky/feed/getTimeline.json | 7 +- lexicons/app/bsky/feed/like.json | 2 +- lexicons/app/bsky/feed/post.json | 21 +- lexicons/app/bsky/feed/repost.json | 2 +- lexicons/app/bsky/feed/searchPosts.json | 2 +- lexicons/app/bsky/feed/threadgate.json | 8 +- lexicons/app/bsky/graph/block.json | 8 +- lexicons/app/bsky/graph/follow.json | 2 +- lexicons/app/bsky/graph/getBlocks.json | 2 +- lexicons/app/bsky/graph/getFollowers.json | 2 +- lexicons/app/bsky/graph/getFollows.json | 2 +- lexicons/app/bsky/graph/getList.json | 8 +- lexicons/app/bsky/graph/getListBlocks.json | 2 +- lexicons/app/bsky/graph/getListMutes.json | 2 +- lexicons/app/bsky/graph/getLists.json | 8 +- lexicons/app/bsky/graph/getMutes.json | 2 +- lexicons/app/bsky/graph/getRelationships.json | 9 +- .../graph/getSuggestedFollowsByActor.json | 2 +- lexicons/app/bsky/graph/list.json | 10 +- lexicons/app/bsky/graph/listblock.json | 8 +- lexicons/app/bsky/graph/listitem.json | 14 +- lexicons/app/bsky/graph/muteActor.json | 2 +- lexicons/app/bsky/graph/muteActorList.json | 2 +- lexicons/app/bsky/graph/unmuteActor.json | 2 +- lexicons/app/bsky/graph/unmuteActorList.json | 2 +- .../app/bsky/notification/getUnreadCount.json | 2 +- .../bsky/notification/listNotifications.json | 2 +- .../app/bsky/notification/registerPush.json | 2 +- .../app/bsky/notification/updateSeen.json | 2 +- lexicons/app/bsky/richtext/facet.json | 9 +- .../com/atproto/identity/resolveHandle.json | 2 +- .../com/atproto/identity/updateHandle.json | 8 +- lexicons/com/atproto/label/queryLabels.json | 2 +- .../com/atproto/label/subscribeLabels.json | 4 +- .../com/atproto/moderation/createReport.json | 8 +- lexicons/com/atproto/repo/applyWrites.json | 20 +- lexicons/com/atproto/repo/createRecord.json | 17 +- lexicons/com/atproto/repo/deleteRecord.json | 6 +- lexicons/com/atproto/repo/describeRepo.json | 13 +- lexicons/com/atproto/repo/getRecord.json | 4 +- lexicons/com/atproto/repo/listRecords.json | 2 +- lexicons/com/atproto/repo/putRecord.json | 10 +- lexicons/com/atproto/repo/uploadBlob.json | 2 +- .../com/atproto/server/createAccount.json | 41 +- .../com/atproto/server/createAppPassword.json | 5 +- .../com/atproto/server/deleteAccount.json | 2 +- .../com/atproto/server/deleteSession.json | 2 +- .../com/atproto/server/describeServer.json | 19 +- .../atproto/server/getAccountInviteCodes.json | 8 +- lexicons/com/atproto/server/getSession.json | 2 +- .../com/atproto/server/refreshSession.json | 2 +- .../com/atproto/server/reserveSigningKey.json | 6 +- lexicons/com/atproto/sync/getBlob.json | 4 +- lexicons/com/atproto/sync/getBlocks.json | 2 +- .../com/atproto/sync/getLatestCommit.json | 2 +- lexicons/com/atproto/sync/getRecord.json | 4 +- lexicons/com/atproto/sync/getRepo.json | 4 +- lexicons/com/atproto/sync/listBlobs.json | 2 +- lexicons/com/atproto/sync/listRepos.json | 8 +- lexicons/com/atproto/sync/notifyOfUpdate.json | 4 +- lexicons/com/atproto/sync/requestCrawl.json | 4 +- lexicons/com/atproto/sync/subscribeRepos.json | 74 ++- lexicons/com/atproto/temp/fetchLabels.json | 2 +- .../com/atproto/temp/transferAccount.json | 2 +- packages/api/src/client/lexicons.ts | 458 +++++++++++++----- .../src/client/types/app/bsky/actor/defs.ts | 1 + .../client/types/app/bsky/actor/getProfile.ts | 1 + .../client/types/app/bsky/actor/profile.ts | 3 + .../client/types/app/bsky/embed/external.ts | 1 + .../src/client/types/app/bsky/embed/images.ts | 4 + .../src/client/types/app/bsky/embed/record.ts | 1 + .../src/client/types/app/bsky/feed/defs.ts | 1 + .../types/app/bsky/feed/getAuthorFeed.ts | 1 + .../types/app/bsky/feed/getFeedGenerator.ts | 3 + .../types/app/bsky/feed/getFeedSkeleton.ts | 1 + .../client/types/app/bsky/feed/getLikes.ts | 2 + .../client/types/app/bsky/feed/getListFeed.ts | 1 + .../types/app/bsky/feed/getPostThread.ts | 3 + .../client/types/app/bsky/feed/getPosts.ts | 1 + .../types/app/bsky/feed/getRepostedBy.ts | 2 + .../client/types/app/bsky/feed/getTimeline.ts | 1 + .../src/client/types/app/bsky/feed/post.ts | 6 +- .../client/types/app/bsky/feed/threadgate.ts | 1 + .../src/client/types/app/bsky/graph/block.ts | 1 + .../client/types/app/bsky/graph/getList.ts | 1 + .../client/types/app/bsky/graph/getLists.ts | 1 + .../types/app/bsky/graph/getRelationships.ts | 2 + .../src/client/types/app/bsky/graph/list.ts | 1 + .../client/types/app/bsky/graph/listblock.ts | 1 + .../client/types/app/bsky/graph/listitem.ts | 2 + .../client/types/app/bsky/richtext/facet.ts | 9 +- .../com/atproto/identity/updateHandle.ts | 1 + .../com/atproto/moderation/createReport.ts | 1 + .../types/com/atproto/repo/applyWrites.ts | 11 +- .../types/com/atproto/repo/createRecord.ts | 8 +- .../types/com/atproto/repo/deleteRecord.ts | 4 +- .../types/com/atproto/repo/describeRepo.ts | 3 + .../types/com/atproto/repo/getRecord.ts | 2 +- .../types/com/atproto/repo/putRecord.ts | 8 +- .../types/com/atproto/server/createAccount.ts | 8 + .../com/atproto/server/createAppPassword.ts | 1 + .../com/atproto/server/describeServer.ts | 3 + .../atproto/server/getAccountInviteCodes.ts | 1 + .../com/atproto/server/reserveSigningKey.ts | 4 +- .../client/types/com/atproto/sync/getBlob.ts | 2 +- .../types/com/atproto/sync/getRecord.ts | 1 + .../client/types/com/atproto/sync/getRepo.ts | 2 +- .../types/com/atproto/sync/listRepos.ts | 1 + .../types/com/atproto/sync/notifyOfUpdate.ts | 2 +- .../types/com/atproto/sync/requestCrawl.ts | 2 +- .../types/com/atproto/sync/subscribeRepos.ts | 20 +- packages/bsky/src/lexicon/lexicons.ts | 458 +++++++++++++----- .../src/lexicon/types/app/bsky/actor/defs.ts | 1 + .../types/app/bsky/actor/getProfile.ts | 1 + .../lexicon/types/app/bsky/actor/profile.ts | 3 + .../lexicon/types/app/bsky/embed/external.ts | 1 + .../lexicon/types/app/bsky/embed/images.ts | 4 + .../lexicon/types/app/bsky/embed/record.ts | 1 + .../src/lexicon/types/app/bsky/feed/defs.ts | 1 + .../types/app/bsky/feed/getAuthorFeed.ts | 1 + .../types/app/bsky/feed/getFeedGenerator.ts | 3 + .../types/app/bsky/feed/getFeedSkeleton.ts | 1 + .../lexicon/types/app/bsky/feed/getLikes.ts | 2 + .../types/app/bsky/feed/getListFeed.ts | 1 + .../types/app/bsky/feed/getPostThread.ts | 3 + .../lexicon/types/app/bsky/feed/getPosts.ts | 1 + .../types/app/bsky/feed/getRepostedBy.ts | 2 + .../types/app/bsky/feed/getTimeline.ts | 1 + .../src/lexicon/types/app/bsky/feed/post.ts | 6 +- .../lexicon/types/app/bsky/feed/threadgate.ts | 1 + .../src/lexicon/types/app/bsky/graph/block.ts | 1 + .../lexicon/types/app/bsky/graph/getList.ts | 1 + .../lexicon/types/app/bsky/graph/getLists.ts | 1 + .../types/app/bsky/graph/getRelationships.ts | 2 + .../src/lexicon/types/app/bsky/graph/list.ts | 1 + .../lexicon/types/app/bsky/graph/listblock.ts | 1 + .../lexicon/types/app/bsky/graph/listitem.ts | 2 + .../lexicon/types/app/bsky/richtext/facet.ts | 9 +- .../com/atproto/identity/updateHandle.ts | 1 + .../com/atproto/label/subscribeLabels.ts | 2 +- .../com/atproto/moderation/createReport.ts | 1 + .../types/com/atproto/repo/applyWrites.ts | 11 +- .../types/com/atproto/repo/createRecord.ts | 8 +- .../types/com/atproto/repo/deleteRecord.ts | 4 +- .../types/com/atproto/repo/describeRepo.ts | 3 + .../types/com/atproto/repo/getRecord.ts | 2 +- .../types/com/atproto/repo/putRecord.ts | 8 +- .../types/com/atproto/server/createAccount.ts | 8 + .../com/atproto/server/createAppPassword.ts | 1 + .../com/atproto/server/describeServer.ts | 3 + .../atproto/server/getAccountInviteCodes.ts | 1 + .../com/atproto/server/reserveSigningKey.ts | 4 +- .../lexicon/types/com/atproto/sync/getBlob.ts | 2 +- .../types/com/atproto/sync/getRecord.ts | 1 + .../lexicon/types/com/atproto/sync/getRepo.ts | 2 +- .../types/com/atproto/sync/listRepos.ts | 1 + .../types/com/atproto/sync/notifyOfUpdate.ts | 2 +- .../types/com/atproto/sync/requestCrawl.ts | 2 +- .../types/com/atproto/sync/subscribeRepos.ts | 22 +- packages/ozone/src/lexicon/lexicons.ts | 458 +++++++++++++----- .../src/lexicon/types/app/bsky/actor/defs.ts | 1 + .../types/app/bsky/actor/getProfile.ts | 1 + .../lexicon/types/app/bsky/actor/profile.ts | 3 + .../lexicon/types/app/bsky/embed/external.ts | 1 + .../lexicon/types/app/bsky/embed/images.ts | 4 + .../lexicon/types/app/bsky/embed/record.ts | 1 + .../src/lexicon/types/app/bsky/feed/defs.ts | 1 + .../types/app/bsky/feed/getAuthorFeed.ts | 1 + .../types/app/bsky/feed/getFeedGenerator.ts | 3 + .../types/app/bsky/feed/getFeedSkeleton.ts | 1 + .../lexicon/types/app/bsky/feed/getLikes.ts | 2 + .../types/app/bsky/feed/getListFeed.ts | 1 + .../types/app/bsky/feed/getPostThread.ts | 3 + .../lexicon/types/app/bsky/feed/getPosts.ts | 1 + .../types/app/bsky/feed/getRepostedBy.ts | 2 + .../types/app/bsky/feed/getTimeline.ts | 1 + .../src/lexicon/types/app/bsky/feed/post.ts | 6 +- .../lexicon/types/app/bsky/feed/threadgate.ts | 1 + .../src/lexicon/types/app/bsky/graph/block.ts | 1 + .../lexicon/types/app/bsky/graph/getList.ts | 1 + .../lexicon/types/app/bsky/graph/getLists.ts | 1 + .../types/app/bsky/graph/getRelationships.ts | 2 + .../src/lexicon/types/app/bsky/graph/list.ts | 1 + .../lexicon/types/app/bsky/graph/listblock.ts | 1 + .../lexicon/types/app/bsky/graph/listitem.ts | 2 + .../lexicon/types/app/bsky/richtext/facet.ts | 9 +- .../com/atproto/identity/updateHandle.ts | 1 + .../com/atproto/label/subscribeLabels.ts | 2 +- .../com/atproto/moderation/createReport.ts | 1 + .../types/com/atproto/repo/applyWrites.ts | 11 +- .../types/com/atproto/repo/createRecord.ts | 8 +- .../types/com/atproto/repo/deleteRecord.ts | 4 +- .../types/com/atproto/repo/describeRepo.ts | 3 + .../types/com/atproto/repo/getRecord.ts | 2 +- .../types/com/atproto/repo/putRecord.ts | 8 +- .../types/com/atproto/server/createAccount.ts | 8 + .../com/atproto/server/createAppPassword.ts | 1 + .../com/atproto/server/describeServer.ts | 3 + .../atproto/server/getAccountInviteCodes.ts | 1 + .../com/atproto/server/reserveSigningKey.ts | 4 +- .../lexicon/types/com/atproto/sync/getBlob.ts | 2 +- .../types/com/atproto/sync/getRecord.ts | 1 + .../lexicon/types/com/atproto/sync/getRepo.ts | 2 +- .../types/com/atproto/sync/listRepos.ts | 1 + .../types/com/atproto/sync/notifyOfUpdate.ts | 2 +- .../types/com/atproto/sync/requestCrawl.ts | 2 +- .../types/com/atproto/sync/subscribeRepos.ts | 22 +- packages/pds/src/lexicon/lexicons.ts | 458 +++++++++++++----- .../src/lexicon/types/app/bsky/actor/defs.ts | 1 + .../types/app/bsky/actor/getProfile.ts | 1 + .../lexicon/types/app/bsky/actor/profile.ts | 3 + .../lexicon/types/app/bsky/embed/external.ts | 1 + .../lexicon/types/app/bsky/embed/images.ts | 4 + .../lexicon/types/app/bsky/embed/record.ts | 1 + .../src/lexicon/types/app/bsky/feed/defs.ts | 1 + .../types/app/bsky/feed/getAuthorFeed.ts | 1 + .../types/app/bsky/feed/getFeedGenerator.ts | 3 + .../types/app/bsky/feed/getFeedSkeleton.ts | 1 + .../lexicon/types/app/bsky/feed/getLikes.ts | 2 + .../types/app/bsky/feed/getListFeed.ts | 1 + .../types/app/bsky/feed/getPostThread.ts | 3 + .../lexicon/types/app/bsky/feed/getPosts.ts | 1 + .../types/app/bsky/feed/getRepostedBy.ts | 2 + .../types/app/bsky/feed/getTimeline.ts | 1 + .../src/lexicon/types/app/bsky/feed/post.ts | 6 +- .../lexicon/types/app/bsky/feed/threadgate.ts | 1 + .../src/lexicon/types/app/bsky/graph/block.ts | 1 + .../lexicon/types/app/bsky/graph/getList.ts | 1 + .../lexicon/types/app/bsky/graph/getLists.ts | 1 + .../types/app/bsky/graph/getRelationships.ts | 2 + .../src/lexicon/types/app/bsky/graph/list.ts | 1 + .../lexicon/types/app/bsky/graph/listblock.ts | 1 + .../lexicon/types/app/bsky/graph/listitem.ts | 2 + .../lexicon/types/app/bsky/richtext/facet.ts | 9 +- .../com/atproto/identity/updateHandle.ts | 1 + .../com/atproto/label/subscribeLabels.ts | 2 +- .../com/atproto/moderation/createReport.ts | 1 + .../types/com/atproto/repo/applyWrites.ts | 11 +- .../types/com/atproto/repo/createRecord.ts | 8 +- .../types/com/atproto/repo/deleteRecord.ts | 4 +- .../types/com/atproto/repo/describeRepo.ts | 3 + .../types/com/atproto/repo/getRecord.ts | 2 +- .../types/com/atproto/repo/putRecord.ts | 8 +- .../types/com/atproto/server/createAccount.ts | 8 + .../com/atproto/server/createAppPassword.ts | 1 + .../com/atproto/server/describeServer.ts | 3 + .../atproto/server/getAccountInviteCodes.ts | 1 + .../com/atproto/server/reserveSigningKey.ts | 4 +- .../lexicon/types/com/atproto/sync/getBlob.ts | 2 +- .../types/com/atproto/sync/getRecord.ts | 1 + .../lexicon/types/com/atproto/sync/getRepo.ts | 2 +- .../types/com/atproto/sync/listRepos.ts | 1 + .../types/com/atproto/sync/notifyOfUpdate.ts | 2 +- .../types/com/atproto/sync/requestCrawl.ts | 2 +- .../types/com/atproto/sync/subscribeRepos.ts | 22 +- 281 files changed, 2185 insertions(+), 801 deletions(-) diff --git a/lexicons/app/bsky/actor/defs.json b/lexicons/app/bsky/actor/defs.json index 9f8e2ea97c8..b4499bcb7cc 100644 --- a/lexicons/app/bsky/actor/defs.json +++ b/lexicons/app/bsky/actor/defs.json @@ -1,7 +1,6 @@ { "lexicon": 1, "id": "app.bsky.actor.defs", - "description": "A reference to an actor in the network.", "defs": { "profileViewBasic": { "type": "object", @@ -78,6 +77,7 @@ }, "viewerState": { "type": "object", + "description": "Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests.", "properties": { "muted": { "type": "boolean" }, "mutedByList": { diff --git a/lexicons/app/bsky/actor/getPreferences.json b/lexicons/app/bsky/actor/getPreferences.json index cbd6b60bd6a..e6356a86f47 100644 --- a/lexicons/app/bsky/actor/getPreferences.json +++ b/lexicons/app/bsky/actor/getPreferences.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get private preferences attached to the account.", + "description": "Get private preferences attached to the current account. Expected use is synchronization between multiple devices, and import/export during account migration. Requires auth.", "parameters": { "type": "params", "properties": {} diff --git a/lexicons/app/bsky/actor/getProfile.json b/lexicons/app/bsky/actor/getProfile.json index 1bb2ad2fea1..15b0fcc2ec0 100644 --- a/lexicons/app/bsky/actor/getProfile.json +++ b/lexicons/app/bsky/actor/getProfile.json @@ -4,12 +4,16 @@ "defs": { "main": { "type": "query", - "description": "Get detailed profile view of an actor.", + "description": "Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth.", "parameters": { "type": "params", "required": ["actor"], "properties": { - "actor": { "type": "string", "format": "at-identifier" } + "actor": { + "type": "string", + "format": "at-identifier", + "description": "Handle or DID of account to fetch profile of." + } } }, "output": { diff --git a/lexicons/app/bsky/actor/getSuggestions.json b/lexicons/app/bsky/actor/getSuggestions.json index 74465dfdf2e..2004ae6f23e 100644 --- a/lexicons/app/bsky/actor/getSuggestions.json +++ b/lexicons/app/bsky/actor/getSuggestions.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get a list of suggested actors, used for discovery.", + "description": "Get a list of suggested actors. Expected use is discovery of accounts to follow during new account onboarding.", "parameters": { "type": "params", "properties": { diff --git a/lexicons/app/bsky/actor/profile.json b/lexicons/app/bsky/actor/profile.json index e1b7c6a2b96..feb083d500a 100644 --- a/lexicons/app/bsky/actor/profile.json +++ b/lexicons/app/bsky/actor/profile.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "record", - "description": "A declaration of a profile.", + "description": "A declaration of a Bluesky account profile.", "key": "literal:self", "record": { "type": "object", @@ -16,21 +16,25 @@ }, "description": { "type": "string", + "description": "Free-form profile description text.", "maxGraphemes": 256, "maxLength": 2560 }, "avatar": { "type": "blob", + "description": "Small image to be displayed next to posts from account. AKA, 'profile picture'", "accept": ["image/png", "image/jpeg"], "maxSize": 1000000 }, "banner": { "type": "blob", + "description": "Larger horizontal image to display behind profile view.", "accept": ["image/png", "image/jpeg"], "maxSize": 1000000 }, "labels": { "type": "union", + "description": "Self-label values, specific to the Bluesky application, on the overall account.", "refs": ["com.atproto.label.defs#selfLabels"] } } diff --git a/lexicons/app/bsky/actor/searchActors.json b/lexicons/app/bsky/actor/searchActors.json index 48fbacf4fcc..15ccb082238 100644 --- a/lexicons/app/bsky/actor/searchActors.json +++ b/lexicons/app/bsky/actor/searchActors.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Find actors (profiles) matching search criteria.", + "description": "Find actors (profiles) matching search criteria. Does not require auth.", "parameters": { "type": "params", "properties": { diff --git a/lexicons/app/bsky/actor/searchActorsTypeahead.json b/lexicons/app/bsky/actor/searchActorsTypeahead.json index 495b7081c38..4e3cb1b4e88 100644 --- a/lexicons/app/bsky/actor/searchActorsTypeahead.json +++ b/lexicons/app/bsky/actor/searchActorsTypeahead.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Find actor suggestions for a prefix search term.", + "description": "Find actor suggestions for a prefix search term. Expected use is for auto-completion during text field entry. Does not require auth.", "parameters": { "type": "params", "properties": { diff --git a/lexicons/app/bsky/embed/external.json b/lexicons/app/bsky/embed/external.json index 8946382835f..b9c8c1596d5 100644 --- a/lexicons/app/bsky/embed/external.json +++ b/lexicons/app/bsky/embed/external.json @@ -1,10 +1,10 @@ { "lexicon": 1, "id": "app.bsky.embed.external", - "description": "A representation of some externally linked content, embedded in another form of content.", "defs": { "main": { "type": "object", + "description": "A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post).", "required": ["external"], "properties": { "external": { diff --git a/lexicons/app/bsky/embed/images.json b/lexicons/app/bsky/embed/images.json index 5baa7ab3f74..307607bb7c2 100644 --- a/lexicons/app/bsky/embed/images.json +++ b/lexicons/app/bsky/embed/images.json @@ -1,7 +1,7 @@ { "lexicon": 1, "id": "app.bsky.embed.images", - "description": "A set of images embedded in some other form of content.", + "description": "A set of images embedded in a Bluesky record (eg, a post).", "defs": { "main": { "type": "object", @@ -23,7 +23,10 @@ "accept": ["image/*"], "maxSize": 1000000 }, - "alt": { "type": "string" }, + "alt": { + "type": "string", + "description": "Alt text description of the image, for accessibility." + }, "aspectRatio": { "type": "ref", "ref": "#aspectRatio" } } }, @@ -51,9 +54,18 @@ "type": "object", "required": ["thumb", "fullsize", "alt"], "properties": { - "thumb": { "type": "string" }, - "fullsize": { "type": "string" }, - "alt": { "type": "string" }, + "thumb": { + "type": "string", + "description": "Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View." + }, + "fullsize": { + "type": "string", + "description": "Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View." + }, + "alt": { + "type": "string", + "description": "Alt text description of the image, for accessibility." + }, "aspectRatio": { "type": "ref", "ref": "#aspectRatio" } } } diff --git a/lexicons/app/bsky/embed/record.json b/lexicons/app/bsky/embed/record.json index 4b3d4f814a5..fff9730237d 100644 --- a/lexicons/app/bsky/embed/record.json +++ b/lexicons/app/bsky/embed/record.json @@ -1,7 +1,7 @@ { "lexicon": 1, "id": "app.bsky.embed.record", - "description": "A representation of a record embedded in another form of content.", + "description": "A representation of a record embedded in a Bluesky record (eg, a post). For example, a quote-post, or sharing a feed generator record.", "defs": { "main": { "type": "object", @@ -36,7 +36,10 @@ "type": "ref", "ref": "app.bsky.actor.defs#profileViewBasic" }, - "value": { "type": "unknown" }, + "value": { + "type": "unknown", + "description": "The record data itself." + }, "labels": { "type": "array", "items": { "type": "ref", "ref": "com.atproto.label.defs#label" } diff --git a/lexicons/app/bsky/embed/recordWithMedia.json b/lexicons/app/bsky/embed/recordWithMedia.json index 9bc5fe09048..46145464fe2 100644 --- a/lexicons/app/bsky/embed/recordWithMedia.json +++ b/lexicons/app/bsky/embed/recordWithMedia.json @@ -1,7 +1,7 @@ { "lexicon": 1, "id": "app.bsky.embed.recordWithMedia", - "description": "A representation of a record embedded in another form of content, alongside other compatible embeds.", + "description": "A representation of a record embedded in a Bluesky record (eg, a post), alongside other compatible embeds. For example, a quote post and image, or a quote post and external URL card.", "defs": { "main": { "type": "object", diff --git a/lexicons/app/bsky/feed/defs.json b/lexicons/app/bsky/feed/defs.json index 15a7cb7a719..7f121e88403 100644 --- a/lexicons/app/bsky/feed/defs.json +++ b/lexicons/app/bsky/feed/defs.json @@ -36,6 +36,7 @@ }, "viewerState": { "type": "object", + "description": "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests.", "properties": { "repost": { "type": "string", "format": "at-uri" }, "like": { "type": "string", "format": "at-uri" }, diff --git a/lexicons/app/bsky/feed/describeFeedGenerator.json b/lexicons/app/bsky/feed/describeFeedGenerator.json index f95027183a1..0c7b8c8638e 100644 --- a/lexicons/app/bsky/feed/describeFeedGenerator.json +++ b/lexicons/app/bsky/feed/describeFeedGenerator.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get information about a feed generator, including policies and offered feed URIs.", + "description": "Get information about a feed generator, including policies and offered feed URIs. Does not require auth; implemented by Feed Generator services (not App View).", "output": { "encoding": "application/json", "schema": { diff --git a/lexicons/app/bsky/feed/generator.json b/lexicons/app/bsky/feed/generator.json index 8c00884ad28..d0e361b72cb 100644 --- a/lexicons/app/bsky/feed/generator.json +++ b/lexicons/app/bsky/feed/generator.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "record", - "description": "A declaration of the existence of a feed generator.", + "description": "Record declaring of the existence of a feed generator, and containing metadata about it. The record can exist in any repository.", "key": "any", "record": { "type": "object", @@ -32,6 +32,7 @@ }, "labels": { "type": "union", + "description": "Self-label values", "refs": ["com.atproto.label.defs#selfLabels"] }, "createdAt": { "type": "string", "format": "datetime" } diff --git a/lexicons/app/bsky/feed/getActorFeeds.json b/lexicons/app/bsky/feed/getActorFeeds.json index a0620477bc3..9a7dae0ad5d 100644 --- a/lexicons/app/bsky/feed/getActorFeeds.json +++ b/lexicons/app/bsky/feed/getActorFeeds.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get a list of feeds created by the actor.", + "description": "Get a list of feeds (feed generator records) created by the actor (in the actor's repo).", "parameters": { "type": "params", "required": ["actor"], diff --git a/lexicons/app/bsky/feed/getActorLikes.json b/lexicons/app/bsky/feed/getActorLikes.json index b3baa58a457..22f8ed984ac 100644 --- a/lexicons/app/bsky/feed/getActorLikes.json +++ b/lexicons/app/bsky/feed/getActorLikes.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get a list of posts liked by an actor.", + "description": "Get a list of posts liked by an actor. Does not require auth.", "parameters": { "type": "params", "required": ["actor"], diff --git a/lexicons/app/bsky/feed/getAuthorFeed.json b/lexicons/app/bsky/feed/getAuthorFeed.json index 1939fa9a49d..90e4d1a7708 100644 --- a/lexicons/app/bsky/feed/getAuthorFeed.json +++ b/lexicons/app/bsky/feed/getAuthorFeed.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get a view of an actor's feed.", + "description": "Get a view of an actor's 'author feed' (post and reposts by the author). Does not require auth.", "parameters": { "type": "params", "required": ["actor"], @@ -19,6 +19,7 @@ "cursor": { "type": "string" }, "filter": { "type": "string", + "description": "Combinations of post/repost types to include in response.", "knownValues": [ "posts_with_replies", "posts_no_replies", diff --git a/lexicons/app/bsky/feed/getFeed.json b/lexicons/app/bsky/feed/getFeed.json index 84407bde155..ada3098b56f 100644 --- a/lexicons/app/bsky/feed/getFeed.json +++ b/lexicons/app/bsky/feed/getFeed.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get a hydrated feed from an actor's selected feed generator.", + "description": "Get a hydrated feed from an actor's selected feed generator. Implemented by App View.", "parameters": { "type": "params", "required": ["feed"], diff --git a/lexicons/app/bsky/feed/getFeedGenerator.json b/lexicons/app/bsky/feed/getFeedGenerator.json index 8b3d4d0551a..190eca85b26 100644 --- a/lexicons/app/bsky/feed/getFeedGenerator.json +++ b/lexicons/app/bsky/feed/getFeedGenerator.json @@ -4,12 +4,16 @@ "defs": { "main": { "type": "query", - "description": "Get information about a feed generator.", + "description": "Get information about a feed generator. Implemented by AppView.", "parameters": { "type": "params", "required": ["feed"], "properties": { - "feed": { "type": "string", "format": "at-uri" } + "feed": { + "type": "string", + "format": "at-uri", + "description": "AT-URI of the feed generator record." + } } }, "output": { @@ -22,8 +26,14 @@ "type": "ref", "ref": "app.bsky.feed.defs#generatorView" }, - "isOnline": { "type": "boolean" }, - "isValid": { "type": "boolean" } + "isOnline": { + "type": "boolean", + "description": "Indicates whether the feed generator service has been online recently, or else seems to be inactive." + }, + "isValid": { + "type": "boolean", + "description": "Indicates whether the feed generator service is compatible with the record declaration." + } } } } diff --git a/lexicons/app/bsky/feed/getFeedSkeleton.json b/lexicons/app/bsky/feed/getFeedSkeleton.json index 03f3ba04c0f..2bcaa13d4f0 100644 --- a/lexicons/app/bsky/feed/getFeedSkeleton.json +++ b/lexicons/app/bsky/feed/getFeedSkeleton.json @@ -4,12 +4,16 @@ "defs": { "main": { "type": "query", - "description": "Get a skeleton of a feed provided by a feed generator.", + "description": "Get a skeleton of a feed provided by a feed generator. Auth is optional, depending on provider requirements, and provides the DID of the requester. Implemented by Feed Generator Service.", "parameters": { "type": "params", "required": ["feed"], "properties": { - "feed": { "type": "string", "format": "at-uri" }, + "feed": { + "type": "string", + "format": "at-uri", + "description": "Reference to feed generator record describing the specific feed being requested." + }, "limit": { "type": "integer", "minimum": 1, diff --git a/lexicons/app/bsky/feed/getLikes.json b/lexicons/app/bsky/feed/getLikes.json index ffcbc01ac53..d2c5b1a77df 100644 --- a/lexicons/app/bsky/feed/getLikes.json +++ b/lexicons/app/bsky/feed/getLikes.json @@ -4,13 +4,21 @@ "defs": { "main": { "type": "query", - "description": "Get the list of likes.", + "description": "Get like records which reference a subject (by AT-URI and CID).", "parameters": { "type": "params", "required": ["uri"], "properties": { - "uri": { "type": "string", "format": "at-uri" }, - "cid": { "type": "string", "format": "cid" }, + "uri": { + "type": "string", + "format": "at-uri", + "description": "AT-URI of the subject (eg, a post record)." + }, + "cid": { + "type": "string", + "format": "cid", + "description": "CID of the subject record (aka, specific version of record), to filter likes." + }, "limit": { "type": "integer", "minimum": 1, diff --git a/lexicons/app/bsky/feed/getListFeed.json b/lexicons/app/bsky/feed/getListFeed.json index 4c5358fcfd7..9dd9fdc70f3 100644 --- a/lexicons/app/bsky/feed/getListFeed.json +++ b/lexicons/app/bsky/feed/getListFeed.json @@ -4,12 +4,16 @@ "defs": { "main": { "type": "query", - "description": "Get a view of a recent posts from actors in a list.", + "description": "Get a feed of recent posts from a list (posts and reposts from any actors on the list). Does not require auth.", "parameters": { "type": "params", "required": ["list"], "properties": { - "list": { "type": "string", "format": "at-uri" }, + "list": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) to the list record." + }, "limit": { "type": "integer", "minimum": 1, diff --git a/lexicons/app/bsky/feed/getPostThread.json b/lexicons/app/bsky/feed/getPostThread.json index b983617041f..89e99d9c6d7 100644 --- a/lexicons/app/bsky/feed/getPostThread.json +++ b/lexicons/app/bsky/feed/getPostThread.json @@ -4,20 +4,26 @@ "defs": { "main": { "type": "query", - "description": "Get posts in a thread.", + "description": "Get posts in a thread. Does not require auth, but additional metadata and filtering will be applied for authed requests.", "parameters": { "type": "params", "required": ["uri"], "properties": { - "uri": { "type": "string", "format": "at-uri" }, + "uri": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) to post record." + }, "depth": { "type": "integer", + "description": "How many levels of reply depth should be included in response.", "default": 6, "minimum": 0, "maximum": 1000 }, "parentHeight": { "type": "integer", + "description": "How many levels of parent (and grandparent, etc) post to include.", "default": 80, "minimum": 0, "maximum": 1000 diff --git a/lexicons/app/bsky/feed/getPosts.json b/lexicons/app/bsky/feed/getPosts.json index c985a5cf033..e555ee16326 100644 --- a/lexicons/app/bsky/feed/getPosts.json +++ b/lexicons/app/bsky/feed/getPosts.json @@ -4,13 +4,14 @@ "defs": { "main": { "type": "query", - "description": "Get a view of an actor's feed.", + "description": "Gets post views for a specified list of posts (by AT-URI). This is sometimes referred to as 'hydrating' a 'feed skeleton'.", "parameters": { "type": "params", "required": ["uris"], "properties": { "uris": { "type": "array", + "description": "List of post AT-URIs to return hydrated views for.", "items": { "type": "string", "format": "at-uri" }, "maxLength": 25 } diff --git a/lexicons/app/bsky/feed/getRepostedBy.json b/lexicons/app/bsky/feed/getRepostedBy.json index 99abc6d5cde..db39534658b 100644 --- a/lexicons/app/bsky/feed/getRepostedBy.json +++ b/lexicons/app/bsky/feed/getRepostedBy.json @@ -4,13 +4,21 @@ "defs": { "main": { "type": "query", - "description": "Get a list of reposts.", + "description": "Get a list of reposts for a given post.", "parameters": { "type": "params", "required": ["uri"], "properties": { - "uri": { "type": "string", "format": "at-uri" }, - "cid": { "type": "string", "format": "cid" }, + "uri": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) of post record" + }, + "cid": { + "type": "string", + "format": "cid", + "description": "If supplied, filters to reposts of specific version (by CID) of the post record." + }, "limit": { "type": "integer", "minimum": 1, diff --git a/lexicons/app/bsky/feed/getSuggestedFeeds.json b/lexicons/app/bsky/feed/getSuggestedFeeds.json index de7c4fef753..e643d3391e5 100644 --- a/lexicons/app/bsky/feed/getSuggestedFeeds.json +++ b/lexicons/app/bsky/feed/getSuggestedFeeds.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get a list of suggested feeds for the viewer.", + "description": "Get a list of suggested feeds (feed generators) for the requesting account.", "parameters": { "type": "params", "properties": { diff --git a/lexicons/app/bsky/feed/getTimeline.json b/lexicons/app/bsky/feed/getTimeline.json index c3b116381c6..816380fe680 100644 --- a/lexicons/app/bsky/feed/getTimeline.json +++ b/lexicons/app/bsky/feed/getTimeline.json @@ -4,11 +4,14 @@ "defs": { "main": { "type": "query", - "description": "Get a view of the actor's home timeline.", + "description": "Get a view of the requesting account's home timeline. This is expected to be some form of reverse-chronological feed.", "parameters": { "type": "params", "properties": { - "algorithm": { "type": "string" }, + "algorithm": { + "type": "string", + "description": "Variant 'algorithm' for timeline. Implementation-specific. NOTE: most feed flexibility has been moved to feed generator mechanism." + }, "limit": { "type": "integer", "minimum": 1, diff --git a/lexicons/app/bsky/feed/like.json b/lexicons/app/bsky/feed/like.json index d82f93bbb1b..c0de3b71e92 100644 --- a/lexicons/app/bsky/feed/like.json +++ b/lexicons/app/bsky/feed/like.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "record", - "description": "A declaration of a like.", + "description": "Record declaring a 'like' of a piece of subject content.", "key": "tid", "record": { "type": "object", diff --git a/lexicons/app/bsky/feed/post.json b/lexicons/app/bsky/feed/post.json index d5f92969253..1e3848c4569 100644 --- a/lexicons/app/bsky/feed/post.json +++ b/lexicons/app/bsky/feed/post.json @@ -4,20 +4,26 @@ "defs": { "main": { "type": "record", - "description": "A declaration of a post.", + "description": "Record containing a Bluesky post.", "key": "tid", "record": { "type": "object", "required": ["text", "createdAt"], "properties": { - "text": { "type": "string", "maxLength": 3000, "maxGraphemes": 300 }, + "text": { + "type": "string", + "maxLength": 3000, + "maxGraphemes": 300, + "description": "The primary post content. May be an empty string, if there are embeds." + }, "entities": { "type": "array", - "description": "Deprecated: replaced by app.bsky.richtext.facet.", + "description": "DEPRECATED: replaced by app.bsky.richtext.facet.", "items": { "type": "ref", "ref": "#entity" } }, "facets": { "type": "array", + "description": "Annotations of text (mentions, URLs, hashtags, etc)", "items": { "type": "ref", "ref": "app.bsky.richtext.facet" } }, "reply": { "type": "ref", "ref": "#replyRef" }, @@ -32,20 +38,27 @@ }, "langs": { "type": "array", + "description": "Indicates human language of post primary text content.", "maxLength": 3, "items": { "type": "string", "format": "language" } }, "labels": { "type": "union", + "description": "Self-label values for this post. Effectively content warnings.", "refs": ["com.atproto.label.defs#selfLabels"] }, "tags": { "type": "array", + "description": "Additional hashtags, in addition to any included in post text and facets.", "maxLength": 8, "items": { "type": "string", "maxLength": 640, "maxGraphemes": 64 }, "description": "Additional non-inline tags describing this post." }, - "createdAt": { "type": "string", "format": "datetime" } + "createdAt": { + "type": "string", + "format": "datetime", + "description": "Client-declared timestamp when this post was originally created." + } } } }, diff --git a/lexicons/app/bsky/feed/repost.json b/lexicons/app/bsky/feed/repost.json index 4dbef10b319..028fd627152 100644 --- a/lexicons/app/bsky/feed/repost.json +++ b/lexicons/app/bsky/feed/repost.json @@ -3,7 +3,7 @@ "id": "app.bsky.feed.repost", "defs": { "main": { - "description": "A declaration of a repost.", + "description": "Record representing a 'repost' of an existing Bluesky post.", "type": "record", "key": "tid", "record": { diff --git a/lexicons/app/bsky/feed/searchPosts.json b/lexicons/app/bsky/feed/searchPosts.json index a3e0bc47f03..c89655dd9db 100644 --- a/lexicons/app/bsky/feed/searchPosts.json +++ b/lexicons/app/bsky/feed/searchPosts.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Find posts matching search criteria.", + "description": "Find posts matching search criteria, returning views of those posts.", "parameters": { "type": "params", "required": ["q"], diff --git a/lexicons/app/bsky/feed/threadgate.json b/lexicons/app/bsky/feed/threadgate.json index 7969b6360a6..ff258da4d30 100644 --- a/lexicons/app/bsky/feed/threadgate.json +++ b/lexicons/app/bsky/feed/threadgate.json @@ -5,12 +5,16 @@ "main": { "type": "record", "key": "tid", - "description": "Defines interaction gating rules for a thread. The rkey of the threadgate record should match the rkey of the thread's root post.", + "description": "Record defining interaction gating rules for a thread (aka, reply controls). The record key (rkey) of the threadgate record must match the record key of the thread's root post, and that record must be in the same repository..", "record": { "type": "object", "required": ["post", "createdAt"], "properties": { - "post": { "type": "string", "format": "at-uri" }, + "post": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) to the post record." + }, "allow": { "type": "array", "maxLength": 5, diff --git a/lexicons/app/bsky/graph/block.json b/lexicons/app/bsky/graph/block.json index 6231eb04e10..b64b11a956d 100644 --- a/lexicons/app/bsky/graph/block.json +++ b/lexicons/app/bsky/graph/block.json @@ -4,13 +4,17 @@ "defs": { "main": { "type": "record", - "description": "A declaration of a block.", + "description": "Record declaring a 'block' relationship against another account. NOTE: blocks are public in Bluesky; see blog posts for details.", "key": "tid", "record": { "type": "object", "required": ["subject", "createdAt"], "properties": { - "subject": { "type": "string", "format": "did" }, + "subject": { + "type": "string", + "format": "did", + "description": "DID of the account to be blocked." + }, "createdAt": { "type": "string", "format": "datetime" } } } diff --git a/lexicons/app/bsky/graph/follow.json b/lexicons/app/bsky/graph/follow.json index df4f4319d92..dd6347ac76d 100644 --- a/lexicons/app/bsky/graph/follow.json +++ b/lexicons/app/bsky/graph/follow.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "record", - "description": "A declaration of a social follow.", + "description": "Record declaring a social 'follow' relationship of another account. Duplicate follows will be ignored by the AppView.", "key": "tid", "record": { "type": "object", diff --git a/lexicons/app/bsky/graph/getBlocks.json b/lexicons/app/bsky/graph/getBlocks.json index bbfe956fbe0..79a28f66a52 100644 --- a/lexicons/app/bsky/graph/getBlocks.json +++ b/lexicons/app/bsky/graph/getBlocks.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get a list of who the actor is blocking.", + "description": "Enumerates which accounts the requesting account is currently blocking. Requires auth.", "parameters": { "type": "params", "properties": { diff --git a/lexicons/app/bsky/graph/getFollowers.json b/lexicons/app/bsky/graph/getFollowers.json index 378c7a7a339..a6c4facd653 100644 --- a/lexicons/app/bsky/graph/getFollowers.json +++ b/lexicons/app/bsky/graph/getFollowers.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get a list of an actor's followers.", + "description": "Enumerates accounts which follow a specified account (actor).", "parameters": { "type": "params", "required": ["actor"], diff --git a/lexicons/app/bsky/graph/getFollows.json b/lexicons/app/bsky/graph/getFollows.json index b90f7613889..81f1b6abe49 100644 --- a/lexicons/app/bsky/graph/getFollows.json +++ b/lexicons/app/bsky/graph/getFollows.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get a list of who the actor follows.", + "description": "Enumerates accounts which a specified account (actor) follows.", "parameters": { "type": "params", "required": ["actor"], diff --git a/lexicons/app/bsky/graph/getList.json b/lexicons/app/bsky/graph/getList.json index fd24668e5bd..cb95a003a56 100644 --- a/lexicons/app/bsky/graph/getList.json +++ b/lexicons/app/bsky/graph/getList.json @@ -4,12 +4,16 @@ "defs": { "main": { "type": "query", - "description": "Get a list of actors.", + "description": "Gets a 'view' (with additional context) of a specified list.", "parameters": { "type": "params", "required": ["list"], "properties": { - "list": { "type": "string", "format": "at-uri" }, + "list": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) of the list record to hydrate." + }, "limit": { "type": "integer", "minimum": 1, diff --git a/lexicons/app/bsky/graph/getListBlocks.json b/lexicons/app/bsky/graph/getListBlocks.json index 9f9f59821f2..1bc976617ef 100644 --- a/lexicons/app/bsky/graph/getListBlocks.json +++ b/lexicons/app/bsky/graph/getListBlocks.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get lists that the actor is blocking.", + "description": "Get mod lists that the requesting account (actor) is blocking. Requires auth.", "parameters": { "type": "params", "properties": { diff --git a/lexicons/app/bsky/graph/getListMutes.json b/lexicons/app/bsky/graph/getListMutes.json index 8d42ac40f9c..a56a8257643 100644 --- a/lexicons/app/bsky/graph/getListMutes.json +++ b/lexicons/app/bsky/graph/getListMutes.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get lists that the actor is muting.", + "description": "Enumerates mod lists that the requesting account (actor) currently has muted. Requires auth.", "parameters": { "type": "params", "properties": { diff --git a/lexicons/app/bsky/graph/getLists.json b/lexicons/app/bsky/graph/getLists.json index 602dd15307d..127b13e5558 100644 --- a/lexicons/app/bsky/graph/getLists.json +++ b/lexicons/app/bsky/graph/getLists.json @@ -4,12 +4,16 @@ "defs": { "main": { "type": "query", - "description": "Get a list of lists that belong to an actor.", + "description": "Enumerates the lists created by a specified account (actor).", "parameters": { "type": "params", "required": ["actor"], "properties": { - "actor": { "type": "string", "format": "at-identifier" }, + "actor": { + "type": "string", + "format": "at-identifier", + "description": "The account (actor) to enumerate lists from." + }, "limit": { "type": "integer", "minimum": 1, diff --git a/lexicons/app/bsky/graph/getMutes.json b/lexicons/app/bsky/graph/getMutes.json index 8ceae00f607..22eaf0d384c 100644 --- a/lexicons/app/bsky/graph/getMutes.json +++ b/lexicons/app/bsky/graph/getMutes.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get a list of who the actor mutes.", + "description": "Enumerates accounts that the requesting account (actor) currently has muted. Requires auth.", "parameters": { "type": "params", "properties": { diff --git a/lexicons/app/bsky/graph/getRelationships.json b/lexicons/app/bsky/graph/getRelationships.json index ccd495dea7d..03490a25ec2 100644 --- a/lexicons/app/bsky/graph/getRelationships.json +++ b/lexicons/app/bsky/graph/getRelationships.json @@ -4,14 +4,19 @@ "defs": { "main": { "type": "query", - "description": "Enumerates public relationships between one account, and a list of other accounts", + "description": "Enumerates public relationships between one account, and a list of other accounts. Does not require auth.", "parameters": { "type": "params", "required": ["actor"], "properties": { - "actor": { "type": "string", "format": "at-identifier" }, + "actor": { + "type": "string", + "format": "at-identifier", + "description": "Primary account requesting relationships for." + }, "others": { "type": "array", + "description": "List of 'other' accounts to be related back to the primary.", "maxLength": 30, "items": { "type": "string", diff --git a/lexicons/app/bsky/graph/getSuggestedFollowsByActor.json b/lexicons/app/bsky/graph/getSuggestedFollowsByActor.json index 32873a537c9..5b0cfdebb70 100644 --- a/lexicons/app/bsky/graph/getSuggestedFollowsByActor.json +++ b/lexicons/app/bsky/graph/getSuggestedFollowsByActor.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get suggested follows related to a given actor.", + "description": "Enumerates follows similar to a given account (actor). Expected use is to recommend additional accounts immediately after following one account.", "parameters": { "type": "params", "required": ["actor"], diff --git a/lexicons/app/bsky/graph/list.json b/lexicons/app/bsky/graph/list.json index ccc845a6926..131114126d3 100644 --- a/lexicons/app/bsky/graph/list.json +++ b/lexicons/app/bsky/graph/list.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "record", - "description": "A declaration of a list of actors.", + "description": "Record representing a list of accounts (actors). Scope includes both moderation-oriented lists and curration-oriented lists.", "key": "tid", "record": { "type": "object", @@ -12,9 +12,15 @@ "properties": { "purpose": { "type": "ref", + "description": "Defines the purpose of the list (aka, moderation-oriented or curration-oriented)", "ref": "app.bsky.graph.defs#listPurpose" }, - "name": { "type": "string", "maxLength": 64, "minLength": 1 }, + "name": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "description": "Display name for list; can not be empty." + }, "description": { "type": "string", "maxGraphemes": 300, diff --git a/lexicons/app/bsky/graph/listblock.json b/lexicons/app/bsky/graph/listblock.json index b3a839c5316..df2e17f3c30 100644 --- a/lexicons/app/bsky/graph/listblock.json +++ b/lexicons/app/bsky/graph/listblock.json @@ -4,13 +4,17 @@ "defs": { "main": { "type": "record", - "description": "A block of an entire list of actors.", + "description": "Record representing a block relationship against an entire an entire list of accounts (actors).", "key": "tid", "record": { "type": "object", "required": ["subject", "createdAt"], "properties": { - "subject": { "type": "string", "format": "at-uri" }, + "subject": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) to the mod list record." + }, "createdAt": { "type": "string", "format": "datetime" } } } diff --git a/lexicons/app/bsky/graph/listitem.json b/lexicons/app/bsky/graph/listitem.json index 2eafb1340be..adbd96e77da 100644 --- a/lexicons/app/bsky/graph/listitem.json +++ b/lexicons/app/bsky/graph/listitem.json @@ -4,14 +4,22 @@ "defs": { "main": { "type": "record", - "description": "An item under a declared list of actors.", + "description": "Record representing an account's inclusion on a specific list. The AppView will ignore duplicate listitem records.", "key": "tid", "record": { "type": "object", "required": ["subject", "list", "createdAt"], "properties": { - "subject": { "type": "string", "format": "did" }, - "list": { "type": "string", "format": "at-uri" }, + "subject": { + "type": "string", + "format": "did", + "description": "The account which is included on the list." + }, + "list": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) to the list record (app.bsky.graph.list)." + }, "createdAt": { "type": "string", "format": "datetime" } } } diff --git a/lexicons/app/bsky/graph/muteActor.json b/lexicons/app/bsky/graph/muteActor.json index f1c3dd18f64..c2bf09a3b37 100644 --- a/lexicons/app/bsky/graph/muteActor.json +++ b/lexicons/app/bsky/graph/muteActor.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Mute an actor by DID or handle.", + "description": "Creates a mute relationship for the specified account. Mutes are private in Bluesky. Requires auth.", "input": { "encoding": "application/json", "schema": { diff --git a/lexicons/app/bsky/graph/muteActorList.json b/lexicons/app/bsky/graph/muteActorList.json index c75cc783c14..ad05e6349d7 100644 --- a/lexicons/app/bsky/graph/muteActorList.json +++ b/lexicons/app/bsky/graph/muteActorList.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Mute a list of actors.", + "description": "Creates a mute relationship for the specified list of accounts. Mutes are private in Bluesky. Requires auth.", "input": { "encoding": "application/json", "schema": { diff --git a/lexicons/app/bsky/graph/unmuteActor.json b/lexicons/app/bsky/graph/unmuteActor.json index 114af204890..bcea72db59d 100644 --- a/lexicons/app/bsky/graph/unmuteActor.json +++ b/lexicons/app/bsky/graph/unmuteActor.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Unmute an actor by DID or handle.", + "description": "Unmutes the specified account. Requires auth.", "input": { "encoding": "application/json", "schema": { diff --git a/lexicons/app/bsky/graph/unmuteActorList.json b/lexicons/app/bsky/graph/unmuteActorList.json index d9644cddc8e..a597838e112 100644 --- a/lexicons/app/bsky/graph/unmuteActorList.json +++ b/lexicons/app/bsky/graph/unmuteActorList.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Unmute a list of actors.", + "description": "Unmutes the specified list of accounts. Requires auth.", "input": { "encoding": "application/json", "schema": { diff --git a/lexicons/app/bsky/notification/getUnreadCount.json b/lexicons/app/bsky/notification/getUnreadCount.json index ab716b2a436..5eebbbf4eb5 100644 --- a/lexicons/app/bsky/notification/getUnreadCount.json +++ b/lexicons/app/bsky/notification/getUnreadCount.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get the count of unread notifications.", + "description": "Count the number of unread notifications for the requesting account. Requires auth.", "parameters": { "type": "params", "properties": { diff --git a/lexicons/app/bsky/notification/listNotifications.json b/lexicons/app/bsky/notification/listNotifications.json index ea74c5fba53..6c5095e1eb2 100644 --- a/lexicons/app/bsky/notification/listNotifications.json +++ b/lexicons/app/bsky/notification/listNotifications.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get a list of notifications.", + "description": "Enumerate notifications for the requesting account. Requires auth.", "parameters": { "type": "params", "properties": { diff --git a/lexicons/app/bsky/notification/registerPush.json b/lexicons/app/bsky/notification/registerPush.json index 80819ece46f..c4e50d1108c 100644 --- a/lexicons/app/bsky/notification/registerPush.json +++ b/lexicons/app/bsky/notification/registerPush.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Register for push notifications with a service.", + "description": "Register to receive push notifications, via a specified service, for the requesting account. Requires auth.", "input": { "encoding": "application/json", "schema": { diff --git a/lexicons/app/bsky/notification/updateSeen.json b/lexicons/app/bsky/notification/updateSeen.json index 33626343e51..84bb0e7d52f 100644 --- a/lexicons/app/bsky/notification/updateSeen.json +++ b/lexicons/app/bsky/notification/updateSeen.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Notify server that the user has seen notifications.", + "description": "Notify server that the requesting account has seen notifications. Requires auth.", "input": { "encoding": "application/json", "schema": { diff --git a/lexicons/app/bsky/richtext/facet.json b/lexicons/app/bsky/richtext/facet.json index ea8f2cba288..388a3a5e0ef 100644 --- a/lexicons/app/bsky/richtext/facet.json +++ b/lexicons/app/bsky/richtext/facet.json @@ -4,6 +4,7 @@ "defs": { "main": { "type": "object", + "description": "Annotation of a sub-string within rich text.", "required": ["index", "features"], "properties": { "index": { "type": "ref", "ref": "#byteSlice" }, @@ -15,7 +16,7 @@ }, "mention": { "type": "object", - "description": "A facet feature for actor mentions.", + "description": "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID.", "required": ["did"], "properties": { "did": { "type": "string", "format": "did" } @@ -23,7 +24,7 @@ }, "link": { "type": "object", - "description": "A facet feature for links.", + "description": "Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL.", "required": ["uri"], "properties": { "uri": { "type": "string", "format": "uri" } @@ -31,7 +32,7 @@ }, "tag": { "type": "object", - "description": "A hashtag.", + "description": "Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags').", "required": ["tag"], "properties": { "tag": { "type": "string", "maxLength": 640, "maxGraphemes": 64 } @@ -39,7 +40,7 @@ }, "byteSlice": { "type": "object", - "description": "A text segment. Start is inclusive, end is exclusive. Indices are for utf8-encoded strings.", + "description": "Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets.", "required": ["byteStart", "byteEnd"], "properties": { "byteStart": { "type": "integer", "minimum": 0 }, diff --git a/lexicons/com/atproto/identity/resolveHandle.json b/lexicons/com/atproto/identity/resolveHandle.json index ae5aab8f8fc..95885088a7b 100644 --- a/lexicons/com/atproto/identity/resolveHandle.json +++ b/lexicons/com/atproto/identity/resolveHandle.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Provides the DID of a repo.", + "description": "Resolves a handle (domain name) to a DID.", "parameters": { "type": "params", "required": ["handle"], diff --git a/lexicons/com/atproto/identity/updateHandle.json b/lexicons/com/atproto/identity/updateHandle.json index 5fe392bb838..9bb5c347d38 100644 --- a/lexicons/com/atproto/identity/updateHandle.json +++ b/lexicons/com/atproto/identity/updateHandle.json @@ -4,14 +4,18 @@ "defs": { "main": { "type": "procedure", - "description": "Updates the handle of the account.", + "description": "Updates the current account's handle. Verifies handle validity, and updates did:plc document if necessary. Implemented by PDS, and requires auth.", "input": { "encoding": "application/json", "schema": { "type": "object", "required": ["handle"], "properties": { - "handle": { "type": "string", "format": "handle" } + "handle": { + "type": "string", + "format": "handle", + "description": "The new handle." + } } } } diff --git a/lexicons/com/atproto/label/queryLabels.json b/lexicons/com/atproto/label/queryLabels.json index 7b6fbe23d54..6c81cb0bba6 100644 --- a/lexicons/com/atproto/label/queryLabels.json +++ b/lexicons/com/atproto/label/queryLabels.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Find labels relevant to the provided URI patterns.", + "description": "Find labels relevant to the provided AT-URI patterns. Public endpoint for moderation services, though may return different or additional results with auth.", "parameters": { "type": "params", "required": ["uriPatterns"], diff --git a/lexicons/com/atproto/label/subscribeLabels.json b/lexicons/com/atproto/label/subscribeLabels.json index 9813ffc192e..5fb1a852d3d 100644 --- a/lexicons/com/atproto/label/subscribeLabels.json +++ b/lexicons/com/atproto/label/subscribeLabels.json @@ -4,13 +4,13 @@ "defs": { "main": { "type": "subscription", - "description": "Subscribe to label updates.", + "description": "Subscribe to stream of labels (and negations). Public endpoint implemented by mod services. Uses same sequencing scheme as repo event stream.", "parameters": { "type": "params", "properties": { "cursor": { "type": "integer", - "description": "The last known event to backfill from." + "description": "The last known event seq number to backfill from." } } }, diff --git a/lexicons/com/atproto/moderation/createReport.json b/lexicons/com/atproto/moderation/createReport.json index 161d622fcf2..f41d28d0b15 100644 --- a/lexicons/com/atproto/moderation/createReport.json +++ b/lexicons/com/atproto/moderation/createReport.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Report a repo or a record.", + "description": "Submit a moderation report regarding an atproto account or record. Implemented by moderation services (with PDS proxying), and requires auth.", "input": { "encoding": "application/json", "schema": { @@ -13,9 +13,13 @@ "properties": { "reasonType": { "type": "ref", + "description": "Indicates the broad category of violation the report is for.", "ref": "com.atproto.moderation.defs#reasonType" }, - "reason": { "type": "string" }, + "reason": { + "type": "string", + "description": "Additional context about the content and violation." + }, "subject": { "type": "union", "refs": [ diff --git a/lexicons/com/atproto/repo/applyWrites.json b/lexicons/com/atproto/repo/applyWrites.json index 050b6efbfab..427fc84c4a5 100644 --- a/lexicons/com/atproto/repo/applyWrites.json +++ b/lexicons/com/atproto/repo/applyWrites.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Apply a batch transaction of creates, updates, and deletes.", + "description": "Apply a batch transaction of repository creates, updates, and deletes. Requires auth, implemented by PDS.", "input": { "encoding": "application/json", "schema": { @@ -14,12 +14,12 @@ "repo": { "type": "string", "format": "at-identifier", - "description": "The handle or DID of the repo." + "description": "The handle or DID of the repo (aka, current account)." }, "validate": { "type": "boolean", "default": true, - "description": "Flag for validating the records." + "description": "Can be set to 'false' to skip Lexicon schema validation of record data, for all operations." }, "writes": { "type": "array", @@ -31,16 +31,22 @@ }, "swapCommit": { "type": "string", + "description": "If provided, the entire operation will fail if the current repo commit CID does not match this value. Used to prevent conflicting repo mutations.", "format": "cid" } } } }, - "errors": [{ "name": "InvalidSwap" }] + "errors": [ + { + "name": "InvalidSwap", + "description": "Indicates that the 'swapCommit' parameter did not match current commit." + } + ] }, "create": { "type": "object", - "description": "Create a new record.", + "description": "Operation which creates a new record.", "required": ["collection", "value"], "properties": { "collection": { "type": "string", "format": "nsid" }, @@ -50,7 +56,7 @@ }, "update": { "type": "object", - "description": "Update an existing record.", + "description": "Operation which updates an existing record.", "required": ["collection", "rkey", "value"], "properties": { "collection": { "type": "string", "format": "nsid" }, @@ -60,7 +66,7 @@ }, "delete": { "type": "object", - "description": "Delete an existing record.", + "description": "Operation which deletes an existing record.", "required": ["collection", "rkey"], "properties": { "collection": { "type": "string", "format": "nsid" }, diff --git a/lexicons/com/atproto/repo/createRecord.json b/lexicons/com/atproto/repo/createRecord.json index baef20c88f0..185f5250850 100644 --- a/lexicons/com/atproto/repo/createRecord.json +++ b/lexicons/com/atproto/repo/createRecord.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Create a new record.", + "description": "Create a single new repository record. Requires auth, implemented by PDS.", "input": { "encoding": "application/json", "schema": { @@ -14,7 +14,7 @@ "repo": { "type": "string", "format": "at-identifier", - "description": "The handle or DID of the repo." + "description": "The handle or DID of the repo (aka, current account)." }, "collection": { "type": "string", @@ -23,17 +23,17 @@ }, "rkey": { "type": "string", - "description": "The key of the record.", + "description": "The Record Key.", "maxLength": 15 }, "validate": { "type": "boolean", "default": true, - "description": "Flag for validating the record." + "description": "Can be set to 'false' to skip Lexicon schema validation of record data." }, "record": { "type": "unknown", - "description": "The record to create." + "description": "The record itself. Must contain a $type field." }, "swapCommit": { "type": "string", @@ -54,7 +54,12 @@ } } }, - "errors": [{ "name": "InvalidSwap" }] + "errors": [ + { + "name": "InvalidSwap", + "description": "Indicates that 'swapCommit' didn't match current repo commit." + } + ] } } } diff --git a/lexicons/com/atproto/repo/deleteRecord.json b/lexicons/com/atproto/repo/deleteRecord.json index d8d7955b6a9..65b9f8f9536 100644 --- a/lexicons/com/atproto/repo/deleteRecord.json +++ b/lexicons/com/atproto/repo/deleteRecord.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Delete a record, or ensure it doesn't exist.", + "description": "Delete a repository record, or ensure it doesn't exist. Requires auth, implemented by PDS.", "input": { "encoding": "application/json", "schema": { @@ -14,7 +14,7 @@ "repo": { "type": "string", "format": "at-identifier", - "description": "The handle or DID of the repo." + "description": "The handle or DID of the repo (aka, current account)." }, "collection": { "type": "string", @@ -23,7 +23,7 @@ }, "rkey": { "type": "string", - "description": "The key of the record." + "description": "The Record Key." }, "swapRecord": { "type": "string", diff --git a/lexicons/com/atproto/repo/describeRepo.json b/lexicons/com/atproto/repo/describeRepo.json index b7f283bff70..b1ce2b6cf9e 100644 --- a/lexicons/com/atproto/repo/describeRepo.json +++ b/lexicons/com/atproto/repo/describeRepo.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get information about the repo, including the list of collections.", + "description": "Get information about an account and repository, including the list of collections. Does not require auth.", "parameters": { "type": "params", "required": ["repo"], @@ -30,12 +30,19 @@ "properties": { "handle": { "type": "string", "format": "handle" }, "did": { "type": "string", "format": "did" }, - "didDoc": { "type": "unknown" }, + "didDoc": { + "type": "unknown", + "description": "The complete DID document for this account." + }, "collections": { "type": "array", + "description": "List of all the collections (NSIDs) for which this repo contains at least one record.", "items": { "type": "string", "format": "nsid" } }, - "handleIsCorrect": { "type": "boolean" } + "handleIsCorrect": { + "type": "boolean", + "description": "Indicates if handle is currently valid (resolves bi-directionally)" + } } } } diff --git a/lexicons/com/atproto/repo/getRecord.json b/lexicons/com/atproto/repo/getRecord.json index ec4d17e4260..5d8bb173470 100644 --- a/lexicons/com/atproto/repo/getRecord.json +++ b/lexicons/com/atproto/repo/getRecord.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get a record.", + "description": "Get a single record from a repository. Does not require auth.", "parameters": { "type": "params", "required": ["repo", "collection", "rkey"], @@ -19,7 +19,7 @@ "format": "nsid", "description": "The NSID of the record collection." }, - "rkey": { "type": "string", "description": "The key of the record." }, + "rkey": { "type": "string", "description": "The Record Key." }, "cid": { "type": "string", "format": "cid", diff --git a/lexicons/com/atproto/repo/listRecords.json b/lexicons/com/atproto/repo/listRecords.json index ac04e3e8782..bc91c952bb1 100644 --- a/lexicons/com/atproto/repo/listRecords.json +++ b/lexicons/com/atproto/repo/listRecords.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "List a range of records in a collection.", + "description": "List a range of records in a repository, matching a specific collection. Does not require auth.", "parameters": { "type": "params", "required": ["repo", "collection"], diff --git a/lexicons/com/atproto/repo/putRecord.json b/lexicons/com/atproto/repo/putRecord.json index ae39bd95ead..51f11c0f13f 100644 --- a/lexicons/com/atproto/repo/putRecord.json +++ b/lexicons/com/atproto/repo/putRecord.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Write a record, creating or updating it as needed.", + "description": "Write a repository record, creating or updating it as needed. Requires auth, implemented by PDS.", "input": { "encoding": "application/json", "schema": { @@ -15,7 +15,7 @@ "repo": { "type": "string", "format": "at-identifier", - "description": "The handle or DID of the repo." + "description": "The handle or DID of the repo (aka, current account)." }, "collection": { "type": "string", @@ -24,13 +24,13 @@ }, "rkey": { "type": "string", - "description": "The key of the record.", + "description": "The Record Key.", "maxLength": 15 }, "validate": { "type": "boolean", "default": true, - "description": "Flag for validating the record." + "description": "Can be set to 'false' to skip Lexicon schema validation of record data." }, "record": { "type": "unknown", @@ -39,7 +39,7 @@ "swapRecord": { "type": "string", "format": "cid", - "description": "Compare and swap with the previous record by CID." + "description": "Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation" }, "swapCommit": { "type": "string", diff --git a/lexicons/com/atproto/repo/uploadBlob.json b/lexicons/com/atproto/repo/uploadBlob.json index 63d1671bd3e..547a995a051 100644 --- a/lexicons/com/atproto/repo/uploadBlob.json +++ b/lexicons/com/atproto/repo/uploadBlob.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Upload a new blob to be added to repo in a later request.", + "description": "Upload a new blob, to be referenced from a repository record. The blob will be deleted if it is not referenced within a time window (eg, minutes). Blob restrictions (mimetype, size, etc) are enforced when the reference is created. Requires auth, implemented by PDS.", "input": { "encoding": "*/*" }, diff --git a/lexicons/com/atproto/server/createAccount.json b/lexicons/com/atproto/server/createAccount.json index d1456e095ae..b32bbe1569d 100644 --- a/lexicons/com/atproto/server/createAccount.json +++ b/lexicons/com/atproto/server/createAccount.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Create an account.", + "description": "Create an account. Implemented by PDS.", "input": { "encoding": "application/json", "schema": { @@ -12,14 +12,31 @@ "required": ["handle"], "properties": { "email": { "type": "string" }, - "handle": { "type": "string", "format": "handle" }, - "did": { "type": "string", "format": "did" }, + "handle": { + "type": "string", + "format": "handle", + "description": "Requested handle for the account." + }, + "did": { + "type": "string", + "format": "did", + "description": "Pre-existing atproto DID, being imported to a new account." + }, "inviteCode": { "type": "string" }, "verificationCode": { "type": "string" }, "verificationPhone": { "type": "string" }, - "password": { "type": "string" }, - "recoveryKey": { "type": "string" }, - "plcOp": { "type": "unknown" } + "password": { + "type": "string", + "description": "Initial account password. May need to meet instance-specific password strength requirements." + }, + "recoveryKey": { + "type": "string", + "description": "DID PLC rotation key (aka, recovery key) to be included in PLC creation operation." + }, + "plcOp": { + "type": "unknown", + "description": "A signed DID PLC operation to be submitted as part of importing an existing account to this instance. NOTE: this optional field may be updated when full account migration is implemented." + } } } }, @@ -27,13 +44,21 @@ "encoding": "application/json", "schema": { "type": "object", + "description": "Account login session returned on successful account creation.", "required": ["accessJwt", "refreshJwt", "handle", "did"], "properties": { "accessJwt": { "type": "string" }, "refreshJwt": { "type": "string" }, "handle": { "type": "string", "format": "handle" }, - "did": { "type": "string", "format": "did" }, - "didDoc": { "type": "unknown" } + "did": { + "type": "string", + "format": "did", + "description": "The DID of the new account." + }, + "didDoc": { + "type": "unknown", + "description": "Complete DID document." + } } } }, diff --git a/lexicons/com/atproto/server/createAppPassword.json b/lexicons/com/atproto/server/createAppPassword.json index f12e8e2557e..0a60e4e30b0 100644 --- a/lexicons/com/atproto/server/createAppPassword.json +++ b/lexicons/com/atproto/server/createAppPassword.json @@ -11,7 +11,10 @@ "type": "object", "required": ["name"], "properties": { - "name": { "type": "string" } + "name": { + "type": "string", + "description": "A short name for the App Password, to help distinguish them." + } } } }, diff --git a/lexicons/com/atproto/server/deleteAccount.json b/lexicons/com/atproto/server/deleteAccount.json index 3747189dca3..cf4babfe7da 100644 --- a/lexicons/com/atproto/server/deleteAccount.json +++ b/lexicons/com/atproto/server/deleteAccount.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Delete an actor's account with a token and password.", + "description": "Delete an actor's account with a token and password. Can only be called after requesting a deletion token. Requires auth.", "input": { "encoding": "application/json", "schema": { diff --git a/lexicons/com/atproto/server/deleteSession.json b/lexicons/com/atproto/server/deleteSession.json index e05d019024a..807a89dc9bd 100644 --- a/lexicons/com/atproto/server/deleteSession.json +++ b/lexicons/com/atproto/server/deleteSession.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Delete the current session." + "description": "Delete the current session. Requires auth." } } } diff --git a/lexicons/com/atproto/server/describeServer.json b/lexicons/com/atproto/server/describeServer.json index 3c60a58ecaf..908cb2127f9 100644 --- a/lexicons/com/atproto/server/describeServer.json +++ b/lexicons/com/atproto/server/describeServer.json @@ -4,20 +4,31 @@ "defs": { "main": { "type": "query", - "description": "Get a document describing the service's accounts configuration.", + "description": "Describes the server's account creation requirements and capabilities. Implemented by PDS.", "output": { "encoding": "application/json", "schema": { "type": "object", "required": ["availableUserDomains"], "properties": { - "inviteCodeRequired": { "type": "boolean" }, - "phoneVerificationRequired": { "type": "boolean" }, + "inviteCodeRequired": { + "type": "boolean", + "description": "If true, an invite code must be supplied to create an account on this instance." + }, + "phoneVerificationRequired": { + "type": "boolean", + "description": "If true, a phone verification token must be supplied to create an account on this instance." + }, "availableUserDomains": { "type": "array", + "description": "List of domain suffixes that can be used in account handles.", "items": { "type": "string" } }, - "links": { "type": "ref", "ref": "#links" } + "links": { + "type": "ref", + "description": "URLs of service policy documents.", + "ref": "#links" + } } } } diff --git a/lexicons/com/atproto/server/getAccountInviteCodes.json b/lexicons/com/atproto/server/getAccountInviteCodes.json index ac23b11f23f..72f0822703d 100644 --- a/lexicons/com/atproto/server/getAccountInviteCodes.json +++ b/lexicons/com/atproto/server/getAccountInviteCodes.json @@ -4,12 +4,16 @@ "defs": { "main": { "type": "query", - "description": "Get all invite codes for a given account.", + "description": "Get all invite codes for the current account. Requires auth.", "parameters": { "type": "params", "properties": { "includeUsed": { "type": "boolean", "default": true }, - "createAvailable": { "type": "boolean", "default": true } + "createAvailable": { + "type": "boolean", + "default": true, + "description": "Controls whether any new 'earned' but not 'created' invites should be created." + } } }, "output": { diff --git a/lexicons/com/atproto/server/getSession.json b/lexicons/com/atproto/server/getSession.json index 5f7700882da..6b5f280e746 100644 --- a/lexicons/com/atproto/server/getSession.json +++ b/lexicons/com/atproto/server/getSession.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get information about the current session.", + "description": "Get information about the current auth session. Requires auth.", "output": { "encoding": "application/json", "schema": { diff --git a/lexicons/com/atproto/server/refreshSession.json b/lexicons/com/atproto/server/refreshSession.json index 3f4d7fdf272..0b067f86b7f 100644 --- a/lexicons/com/atproto/server/refreshSession.json +++ b/lexicons/com/atproto/server/refreshSession.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Refresh an authentication session.", + "description": "Refresh an authentication session. Requires auth using the 'refreshJwt' (not the 'accessJwt').", "output": { "encoding": "application/json", "schema": { diff --git a/lexicons/com/atproto/server/reserveSigningKey.json b/lexicons/com/atproto/server/reserveSigningKey.json index 3a67ad0a3c8..4a99a34528b 100644 --- a/lexicons/com/atproto/server/reserveSigningKey.json +++ b/lexicons/com/atproto/server/reserveSigningKey.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Reserve a repo signing key for account creation.", + "description": "Reserve a repo signing key, for use with account creation. Necessary so that a DID PLC update operation can be constructed during an account migraiton. Public and does not require auth; implemented by PDS. NOTE: this endpoint may change when full account migration is implemented.", "input": { "encoding": "application/json", "schema": { @@ -12,7 +12,7 @@ "properties": { "did": { "type": "string", - "description": "The did to reserve a new did:key for" + "description": "The DID to reserve a key for." } } } @@ -25,7 +25,7 @@ "properties": { "signingKey": { "type": "string", - "description": "Public signing key in the form of a did:key." + "description": "The public key for the reserved signing key, in did:key serialization." } } } diff --git a/lexicons/com/atproto/sync/getBlob.json b/lexicons/com/atproto/sync/getBlob.json index 23e18a4f3b5..57ece7a9d8a 100644 --- a/lexicons/com/atproto/sync/getBlob.json +++ b/lexicons/com/atproto/sync/getBlob.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get a blob associated with a given repo.", + "description": "Get a blob associated with a given account. Returns the full blob as originally uploaded. Does not require auth; implemented by PDS.", "parameters": { "type": "params", "required": ["did", "cid"], @@ -12,7 +12,7 @@ "did": { "type": "string", "format": "did", - "description": "The DID of the repo." + "description": "The DID of the account." }, "cid": { "type": "string", diff --git a/lexicons/com/atproto/sync/getBlocks.json b/lexicons/com/atproto/sync/getBlocks.json index cf776a0c88f..29dd4971904 100644 --- a/lexicons/com/atproto/sync/getBlocks.json +++ b/lexicons/com/atproto/sync/getBlocks.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get blocks from a given repo.", + "description": "Get data blocks from a given repo, by CID. For example, intermediate MST nodes, or records. Does not require auth; implemented by PDS.", "parameters": { "type": "params", "required": ["did", "cids"], diff --git a/lexicons/com/atproto/sync/getLatestCommit.json b/lexicons/com/atproto/sync/getLatestCommit.json index d8754f09062..ac7faf57570 100644 --- a/lexicons/com/atproto/sync/getLatestCommit.json +++ b/lexicons/com/atproto/sync/getLatestCommit.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get the current commit CID & revision of the repo.", + "description": "Get the current commit CID & revision of the specified repo. Does not require auth.", "parameters": { "type": "params", "required": ["did"], diff --git a/lexicons/com/atproto/sync/getRecord.json b/lexicons/com/atproto/sync/getRecord.json index cbd0ad3a5ac..718245a5195 100644 --- a/lexicons/com/atproto/sync/getRecord.json +++ b/lexicons/com/atproto/sync/getRecord.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Get blocks needed for existence or non-existence of record.", + "description": "Get data blocks needed to prove the existence or non-existence of record in the current version of repo. Does not require auth.", "parameters": { "type": "params", "required": ["did", "collection", "rkey"], @@ -15,7 +15,7 @@ "description": "The DID of the repo." }, "collection": { "type": "string", "format": "nsid" }, - "rkey": { "type": "string" }, + "rkey": { "type": "string", "description": "Record Key" }, "commit": { "type": "string", "format": "cid", diff --git a/lexicons/com/atproto/sync/getRepo.json b/lexicons/com/atproto/sync/getRepo.json index fb68ab670ee..7fa710abfb5 100644 --- a/lexicons/com/atproto/sync/getRepo.json +++ b/lexicons/com/atproto/sync/getRepo.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Gets the DID's repo, optionally catching up from a specific revision.", + "description": "Download a repository export as CAR file. Optionally only a 'diff' since a previous revision. Does not require auth; implemented by PDS.", "parameters": { "type": "params", "required": ["did"], @@ -16,7 +16,7 @@ }, "since": { "type": "string", - "description": "The revision of the repo to catch up from." + "description": "The revision ('rev') of the repo to create a diff from." } } }, diff --git a/lexicons/com/atproto/sync/listBlobs.json b/lexicons/com/atproto/sync/listBlobs.json index 46815eeb49a..b4c954d999a 100644 --- a/lexicons/com/atproto/sync/listBlobs.json +++ b/lexicons/com/atproto/sync/listBlobs.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "List blob CIDs since some revision.", + "description": "List blob CIDso for an account, since some repo revision. Does not require auth; implemented by PDS.", "parameters": { "type": "params", "required": ["did"], diff --git a/lexicons/com/atproto/sync/listRepos.json b/lexicons/com/atproto/sync/listRepos.json index 440e8693d5e..07ae35e2c5e 100644 --- a/lexicons/com/atproto/sync/listRepos.json +++ b/lexicons/com/atproto/sync/listRepos.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "List DIDs and root CIDs of hosted repos.", + "description": "Enumerates all the DID, rev, and commit CID for all repos hosted by this service. Does not require auth; implemented by PDS and Relay.", "parameters": { "type": "params", "properties": { @@ -37,7 +37,11 @@ "required": ["did", "head", "rev"], "properties": { "did": { "type": "string", "format": "did" }, - "head": { "type": "string", "format": "cid" }, + "head": { + "type": "string", + "format": "cid", + "description": "Current repo commit CID" + }, "rev": { "type": "string" } } } diff --git a/lexicons/com/atproto/sync/notifyOfUpdate.json b/lexicons/com/atproto/sync/notifyOfUpdate.json index 48cb4b24678..034a9655a08 100644 --- a/lexicons/com/atproto/sync/notifyOfUpdate.json +++ b/lexicons/com/atproto/sync/notifyOfUpdate.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Notify a crawling service of a recent update; often when a long break between updates causes the connection with the crawling service to break.", + "description": "Notify a crawling service of a recent update, and that crawling should resume. Intended use is after a gap between repo stream events caused the crawling service to disconnect. Does not require auth; implemented by Relay.", "input": { "encoding": "application/json", "schema": { @@ -13,7 +13,7 @@ "properties": { "hostname": { "type": "string", - "description": "Hostname of the service that is notifying of update." + "description": "Hostname of the current service (usually a PDS) that is notifying of update." } } } diff --git a/lexicons/com/atproto/sync/requestCrawl.json b/lexicons/com/atproto/sync/requestCrawl.json index a3520a33180..8e075a376fc 100644 --- a/lexicons/com/atproto/sync/requestCrawl.json +++ b/lexicons/com/atproto/sync/requestCrawl.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Request a service to persistently crawl hosted repos.", + "description": "Request a service to persistently crawl hosted repos. Expected use is new PDS instances declaring their existence to Relays. Does not require auth.", "input": { "encoding": "application/json", "schema": { @@ -13,7 +13,7 @@ "properties": { "hostname": { "type": "string", - "description": "Hostname of the service that is requesting to be crawled." + "description": "Hostname of the current service (eg, PDS) that is requesting to be crawled." } } } diff --git a/lexicons/com/atproto/sync/subscribeRepos.json b/lexicons/com/atproto/sync/subscribeRepos.json index 9a5c0f6153c..73a34a3b49e 100644 --- a/lexicons/com/atproto/sync/subscribeRepos.json +++ b/lexicons/com/atproto/sync/subscribeRepos.json @@ -4,13 +4,13 @@ "defs": { "main": { "type": "subscription", - "description": "Subscribe to repo updates.", + "description": "Repository event stream, aka Firehose endpoint. Outputs repo commits with diff data, and identity update events, for all repositories on the current server. See the atproto specifications for details around stream sequencing, repo versioning, CAR diff format, and more. Public and does not require auth; implemented by PDS and Relay.", "parameters": { "type": "params", "properties": { "cursor": { "type": "integer", - "description": "The last known event to backfill from." + "description": "The last known event seq number to backfill from." } } }, @@ -20,10 +20,17 @@ "refs": ["#commit", "#handle", "#migrate", "#tombstone", "#info"] } }, - "errors": [{ "name": "FutureCursor" }, { "name": "ConsumerTooSlow" }] + "errors": [ + { "name": "FutureCursor" }, + { + "name": "ConsumerTooSlow", + "description": "If the consumer of the stream can not keep up with events, and a backlog gets too large, the server will drop the connection." + } + ] }, "commit": { "type": "object", + "description": "Represents an update of repository state. Note that empty commits are allowed, which include no repo data changes, but an update to rev and signature.", "required": [ "seq", "rebase", @@ -39,39 +46,67 @@ ], "nullable": ["prev", "since"], "properties": { - "seq": { "type": "integer" }, - "rebase": { "type": "boolean" }, - "tooBig": { "type": "boolean" }, - "repo": { "type": "string", "format": "did" }, - "commit": { "type": "cid-link" }, - "prev": { "type": "cid-link" }, + "seq": { + "type": "integer", + "description": "The stream sequence number of this message." + }, + "rebase": { "type": "boolean", "description": "DEPRECATED -- unused" }, + "tooBig": { + "type": "boolean", + "description": "Indicates that this commit contained too many ops, or data size was too large. Consumers will need to make a separate request to get missing data." + }, + "repo": { + "type": "string", + "format": "did", + "description": "The repo this event comes from." + }, + "commit": { + "type": "cid-link", + "description": "Repo commit object CID." + }, + "prev": { + "type": "cid-link", + "description": "DEPRECATED -- unused. WARNING -- nullable and optional; stick with optional to ensure golang interoperability." + }, "rev": { "type": "string", - "description": "The rev of the emitted commit." + "description": "The rev of the emitted commit. Note that this information is also in the commit object included in blocks, unless this is a tooBig event." }, "since": { "type": "string", - "description": "The rev of the last emitted commit from this repo." + "description": "The rev of the last emitted commit from this repo (if any)." }, "blocks": { "type": "bytes", - "description": "CAR file containing relevant blocks.", + "description": "CAR file containing relevant blocks, as a diff since the previous repo state.", "maxLength": 1000000 }, "ops": { "type": "array", - "items": { "type": "ref", "ref": "#repoOp" }, + "items": { + "type": "ref", + "ref": "#repoOp", + "description": "List of repo mutation operations in this commit (eg, records created, updated, or deleted)." + }, "maxLength": 200 }, "blobs": { "type": "array", - "items": { "type": "cid-link" } + "items": { + "type": "cid-link", + "description": "List of new blobs (by CID) referenced by records in this commit." + } }, - "time": { "type": "string", "format": "datetime" } + "time": { + "type": "string", + "format": "datetime", + "description": "Timestamp of when this message was originally broadcast." + } } }, "handle": { "type": "object", + "description": "Represents an update of the account's handle, or transition to/from invalid state.", "required": ["seq", "did", "handle", "time"], "properties": { "seq": { "type": "integer" }, @@ -82,6 +117,7 @@ }, "migrate": { "type": "object", + "description": "Represents an account moving from one PDS instance to another. NOTE: not implemented; full account migration may introduce a new message instead.", "required": ["seq", "did", "migrateTo", "time"], "nullable": ["migrateTo"], "properties": { @@ -93,6 +129,7 @@ }, "tombstone": { "type": "object", + "description": "Indicates that an account has been deleted.", "required": ["seq", "did", "time"], "properties": { "seq": { "type": "integer" }, @@ -115,7 +152,7 @@ }, "repoOp": { "type": "object", - "description": "A repo operation, ie a write of a single record. For creates and updates, CID is the record's CID as of this operation. For deletes, it's null.", + "description": "A repo operation, ie a mutation of a single record.", "required": ["action", "path", "cid"], "nullable": ["cid"], "properties": { @@ -124,7 +161,10 @@ "knownValues": ["create", "update", "delete"] }, "path": { "type": "string" }, - "cid": { "type": "cid-link" } + "cid": { + "type": "cid-link", + "description": "For creates and updates, the new record CID. For deletions, null." + } } } } diff --git a/lexicons/com/atproto/temp/fetchLabels.json b/lexicons/com/atproto/temp/fetchLabels.json index 14e392fd5e7..d76041ac296 100644 --- a/lexicons/com/atproto/temp/fetchLabels.json +++ b/lexicons/com/atproto/temp/fetchLabels.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Fetch all labels from a labeler created after a certain date.", + "description": "Fetch all labels from a labeler created after a certain date. DEPRECATED: use queryLabels or subscribeLabels instead", "parameters": { "type": "params", "properties": { diff --git a/lexicons/com/atproto/temp/transferAccount.json b/lexicons/com/atproto/temp/transferAccount.json index 3cb2035ac0e..d4687d9b6e1 100644 --- a/lexicons/com/atproto/temp/transferAccount.json +++ b/lexicons/com/atproto/temp/transferAccount.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "procedure", - "description": "Transfer an account.", + "description": "Transfer an account. NOTE: temporary method, necessarily how account migration will be implemented.", "input": { "encoding": "application/json", "schema": { diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index 515098a8f49..8fcf9491077 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -1912,7 +1912,7 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Provides the DID of a repo.', + description: 'Resolves a handle (domain name) to a DID.', parameters: { type: 'params', required: ['handle'], @@ -1946,7 +1946,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Updates the handle of the account.', + description: + "Updates the current account's handle. Verifies handle validity, and updates did:plc document if necessary. Implemented by PDS, and requires auth.", input: { encoding: 'application/json', schema: { @@ -1956,6 +1957,7 @@ export const schemaDict = { handle: { type: 'string', format: 'handle', + description: 'The new handle.', }, }, }, @@ -2046,7 +2048,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Find labels relevant to the provided URI patterns.', + description: + 'Find labels relevant to the provided AT-URI patterns. Public endpoint for moderation services, though may return different or additional results with auth.', parameters: { type: 'params', required: ['uriPatterns'], @@ -2107,13 +2110,14 @@ export const schemaDict = { defs: { main: { type: 'subscription', - description: 'Subscribe to label updates.', + description: + 'Subscribe to stream of labels (and negations). Public endpoint implemented by mod services. Uses same sequencing scheme as repo event stream.', parameters: { type: 'params', properties: { cursor: { type: 'integer', - description: 'The last known event to backfill from.', + description: 'The last known event seq number to backfill from.', }, }, }, @@ -2169,7 +2173,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Report a repo or a record.', + description: + 'Submit a moderation report regarding an atproto account or record. Implemented by moderation services (with PDS proxying), and requires auth.', input: { encoding: 'application/json', schema: { @@ -2178,10 +2183,14 @@ export const schemaDict = { properties: { reasonType: { type: 'ref', + description: + 'Indicates the broad category of violation the report is for.', ref: 'lex:com.atproto.moderation.defs#reasonType', }, reason: { type: 'string', + description: + 'Additional context about the content and violation.', }, subject: { type: 'union', @@ -2292,7 +2301,7 @@ export const schemaDict = { main: { type: 'procedure', description: - 'Apply a batch transaction of creates, updates, and deletes.', + 'Apply a batch transaction of repository creates, updates, and deletes. Requires auth, implemented by PDS.', input: { encoding: 'application/json', schema: { @@ -2302,12 +2311,14 @@ export const schemaDict = { repo: { type: 'string', format: 'at-identifier', - description: 'The handle or DID of the repo.', + description: + 'The handle or DID of the repo (aka, current account).', }, validate: { type: 'boolean', default: true, - description: 'Flag for validating the records.', + description: + "Can be set to 'false' to skip Lexicon schema validation of record data, for all operations.", }, writes: { type: 'array', @@ -2323,6 +2334,8 @@ export const schemaDict = { }, swapCommit: { type: 'string', + description: + 'If provided, the entire operation will fail if the current repo commit CID does not match this value. Used to prevent conflicting repo mutations.', format: 'cid', }, }, @@ -2331,12 +2344,14 @@ export const schemaDict = { errors: [ { name: 'InvalidSwap', + description: + "Indicates that the 'swapCommit' parameter did not match current commit.", }, ], }, create: { type: 'object', - description: 'Create a new record.', + description: 'Operation which creates a new record.', required: ['collection', 'value'], properties: { collection: { @@ -2354,7 +2369,7 @@ export const schemaDict = { }, update: { type: 'object', - description: 'Update an existing record.', + description: 'Operation which updates an existing record.', required: ['collection', 'rkey', 'value'], properties: { collection: { @@ -2371,7 +2386,7 @@ export const schemaDict = { }, delete: { type: 'object', - description: 'Delete an existing record.', + description: 'Operation which deletes an existing record.', required: ['collection', 'rkey'], properties: { collection: { @@ -2391,7 +2406,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Create a new record.', + description: + 'Create a single new repository record. Requires auth, implemented by PDS.', input: { encoding: 'application/json', schema: { @@ -2401,7 +2417,8 @@ export const schemaDict = { repo: { type: 'string', format: 'at-identifier', - description: 'The handle or DID of the repo.', + description: + 'The handle or DID of the repo (aka, current account).', }, collection: { type: 'string', @@ -2410,17 +2427,18 @@ export const schemaDict = { }, rkey: { type: 'string', - description: 'The key of the record.', + description: 'The Record Key.', maxLength: 15, }, validate: { type: 'boolean', default: true, - description: 'Flag for validating the record.', + description: + "Can be set to 'false' to skip Lexicon schema validation of record data.", }, record: { type: 'unknown', - description: 'The record to create.', + description: 'The record itself. Must contain a $type field.', }, swapCommit: { type: 'string', @@ -2451,6 +2469,8 @@ export const schemaDict = { errors: [ { name: 'InvalidSwap', + description: + "Indicates that 'swapCommit' didn't match current repo commit.", }, ], }, @@ -2462,7 +2482,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: "Delete a record, or ensure it doesn't exist.", + description: + "Delete a repository record, or ensure it doesn't exist. Requires auth, implemented by PDS.", input: { encoding: 'application/json', schema: { @@ -2472,7 +2493,8 @@ export const schemaDict = { repo: { type: 'string', format: 'at-identifier', - description: 'The handle or DID of the repo.', + description: + 'The handle or DID of the repo (aka, current account).', }, collection: { type: 'string', @@ -2481,7 +2503,7 @@ export const schemaDict = { }, rkey: { type: 'string', - description: 'The key of the record.', + description: 'The Record Key.', }, swapRecord: { type: 'string', @@ -2513,7 +2535,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Get information about the repo, including the list of collections.', + 'Get information about an account and repository, including the list of collections. Does not require auth.', parameters: { type: 'params', required: ['repo'], @@ -2547,9 +2569,12 @@ export const schemaDict = { }, didDoc: { type: 'unknown', + description: 'The complete DID document for this account.', }, collections: { type: 'array', + description: + 'List of all the collections (NSIDs) for which this repo contains at least one record.', items: { type: 'string', format: 'nsid', @@ -2557,6 +2582,8 @@ export const schemaDict = { }, handleIsCorrect: { type: 'boolean', + description: + 'Indicates if handle is currently valid (resolves bi-directionally)', }, }, }, @@ -2570,7 +2597,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a record.', + description: + 'Get a single record from a repository. Does not require auth.', parameters: { type: 'params', required: ['repo', 'collection', 'rkey'], @@ -2587,7 +2615,7 @@ export const schemaDict = { }, rkey: { type: 'string', - description: 'The key of the record.', + description: 'The Record Key.', }, cid: { type: 'string', @@ -2626,7 +2654,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'List a range of records in a collection.', + description: + 'List a range of records in a repository, matching a specific collection. Does not require auth.', parameters: { type: 'params', required: ['repo', 'collection'], @@ -2712,7 +2741,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Write a record, creating or updating it as needed.', + description: + 'Write a repository record, creating or updating it as needed. Requires auth, implemented by PDS.', input: { encoding: 'application/json', schema: { @@ -2723,7 +2753,8 @@ export const schemaDict = { repo: { type: 'string', format: 'at-identifier', - description: 'The handle or DID of the repo.', + description: + 'The handle or DID of the repo (aka, current account).', }, collection: { type: 'string', @@ -2732,13 +2763,14 @@ export const schemaDict = { }, rkey: { type: 'string', - description: 'The key of the record.', + description: 'The Record Key.', maxLength: 15, }, validate: { type: 'boolean', default: true, - description: 'Flag for validating the record.', + description: + "Can be set to 'false' to skip Lexicon schema validation of record data.", }, record: { type: 'unknown', @@ -2748,7 +2780,7 @@ export const schemaDict = { type: 'string', format: 'cid', description: - 'Compare and swap with the previous record by CID.', + 'Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation', }, swapCommit: { type: 'string', @@ -2812,7 +2844,7 @@ export const schemaDict = { main: { type: 'procedure', description: - 'Upload a new blob to be added to repo in a later request.', + 'Upload a new blob, to be referenced from a repository record. The blob will be deleted if it is not referenced within a time window (eg, minutes). Blob restrictions (mimetype, size, etc) are enforced when the reference is created. Requires auth, implemented by PDS.', input: { encoding: '*/*', }, @@ -2877,7 +2909,7 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Create an account.', + description: 'Create an account. Implemented by PDS.', input: { encoding: 'application/json', schema: { @@ -2890,10 +2922,13 @@ export const schemaDict = { handle: { type: 'string', format: 'handle', + description: 'Requested handle for the account.', }, did: { type: 'string', format: 'did', + description: + 'Pre-existing atproto DID, being imported to a new account.', }, inviteCode: { type: 'string', @@ -2906,12 +2941,18 @@ export const schemaDict = { }, password: { type: 'string', + description: + 'Initial account password. May need to meet instance-specific password strength requirements.', }, recoveryKey: { type: 'string', + description: + 'DID PLC rotation key (aka, recovery key) to be included in PLC creation operation.', }, plcOp: { type: 'unknown', + description: + 'A signed DID PLC operation to be submitted as part of importing an existing account to this instance. NOTE: this optional field may be updated when full account migration is implemented.', }, }, }, @@ -2920,6 +2961,8 @@ export const schemaDict = { encoding: 'application/json', schema: { type: 'object', + description: + 'Account login session returned on successful account creation.', required: ['accessJwt', 'refreshJwt', 'handle', 'did'], properties: { accessJwt: { @@ -2935,9 +2978,11 @@ export const schemaDict = { did: { type: 'string', format: 'did', + description: 'The DID of the new account.', }, didDoc: { type: 'unknown', + description: 'Complete DID document.', }, }, }, @@ -2983,6 +3028,8 @@ export const schemaDict = { properties: { name: { type: 'string', + description: + 'A short name for the App Password, to help distinguish them.', }, }, }, @@ -3250,7 +3297,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: "Delete an actor's account with a token and password.", + description: + "Delete an actor's account with a token and password. Can only be called after requesting a deletion token. Requires auth.", input: { encoding: 'application/json', schema: { @@ -3287,7 +3335,7 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Delete the current session.', + description: 'Delete the current session. Requires auth.', }, }, }, @@ -3298,7 +3346,7 @@ export const schemaDict = { main: { type: 'query', description: - "Get a document describing the service's accounts configuration.", + "Describes the server's account creation requirements and capabilities. Implemented by PDS.", output: { encoding: 'application/json', schema: { @@ -3307,18 +3355,25 @@ export const schemaDict = { properties: { inviteCodeRequired: { type: 'boolean', + description: + 'If true, an invite code must be supplied to create an account on this instance.', }, phoneVerificationRequired: { type: 'boolean', + description: + 'If true, a phone verification token must be supplied to create an account on this instance.', }, availableUserDomains: { type: 'array', + description: + 'List of domain suffixes that can be used in account handles.', items: { type: 'string', }, }, links: { type: 'ref', + description: 'URLs of service policy documents.', ref: 'lex:com.atproto.server.describeServer#links', }, }, @@ -3344,7 +3399,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get all invite codes for a given account.', + description: + 'Get all invite codes for the current account. Requires auth.', parameters: { type: 'params', properties: { @@ -3355,6 +3411,8 @@ export const schemaDict = { createAvailable: { type: 'boolean', default: true, + description: + "Controls whether any new 'earned' but not 'created' invites should be created.", }, }, }, @@ -3388,7 +3446,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get information about the current session.', + description: + 'Get information about the current auth session. Requires auth.', output: { encoding: 'application/json', schema: { @@ -3468,7 +3527,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Refresh an authentication session.', + description: + "Refresh an authentication session. Requires auth using the 'refreshJwt' (not the 'accessJwt').", output: { encoding: 'application/json', schema: { @@ -3574,7 +3634,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Reserve a repo signing key for account creation.', + description: + 'Reserve a repo signing key, for use with account creation. Necessary so that a DID PLC update operation can be constructed during an account migraiton. Public and does not require auth; implemented by PDS. NOTE: this endpoint may change when full account migration is implemented.', input: { encoding: 'application/json', schema: { @@ -3582,7 +3643,7 @@ export const schemaDict = { properties: { did: { type: 'string', - description: 'The did to reserve a new did:key for', + description: 'The DID to reserve a key for.', }, }, }, @@ -3595,7 +3656,8 @@ export const schemaDict = { properties: { signingKey: { type: 'string', - description: 'Public signing key in the form of a did:key.', + description: + 'The public key for the reserved signing key, in did:key serialization.', }, }, }, @@ -3702,7 +3764,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a blob associated with a given repo.', + description: + 'Get a blob associated with a given account. Returns the full blob as originally uploaded. Does not require auth; implemented by PDS.', parameters: { type: 'params', required: ['did', 'cid'], @@ -3710,7 +3773,7 @@ export const schemaDict = { did: { type: 'string', format: 'did', - description: 'The DID of the repo.', + description: 'The DID of the account.', }, cid: { type: 'string', @@ -3731,7 +3794,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get blocks from a given repo.', + description: + 'Get data blocks from a given repo, by CID. For example, intermediate MST nodes, or records. Does not require auth; implemented by PDS.', parameters: { type: 'params', required: ['did', 'cids'], @@ -3826,7 +3890,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get the current commit CID & revision of the repo.', + description: + 'Get the current commit CID & revision of the specified repo. Does not require auth.', parameters: { type: 'params', required: ['did'], @@ -3869,7 +3934,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Get blocks needed for existence or non-existence of record.', + 'Get data blocks needed to prove the existence or non-existence of record in the current version of repo. Does not require auth.', parameters: { type: 'params', required: ['did', 'collection', 'rkey'], @@ -3885,6 +3950,7 @@ export const schemaDict = { }, rkey: { type: 'string', + description: 'Record Key', }, commit: { type: 'string', @@ -3906,7 +3972,7 @@ export const schemaDict = { main: { type: 'query', description: - "Gets the DID's repo, optionally catching up from a specific revision.", + "Download a repository export as CAR file. Optionally only a 'diff' since a previous revision. Does not require auth; implemented by PDS.", parameters: { type: 'params', required: ['did'], @@ -3918,7 +3984,8 @@ export const schemaDict = { }, since: { type: 'string', - description: 'The revision of the repo to catch up from.', + description: + "The revision ('rev') of the repo to create a diff from.", }, }, }, @@ -3934,7 +4001,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'List blob CIDs since some revision.', + description: + 'List blob CIDso for an account, since some repo revision. Does not require auth; implemented by PDS.', parameters: { type: 'params', required: ['did'], @@ -3987,7 +4055,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'List DIDs and root CIDs of hosted repos.', + description: + 'Enumerates all the DID, rev, and commit CID for all repos hosted by this service. Does not require auth; implemented by PDS and Relay.', parameters: { type: 'params', properties: { @@ -4033,6 +4102,7 @@ export const schemaDict = { head: { type: 'string', format: 'cid', + description: 'Current repo commit CID', }, rev: { type: 'string', @@ -4048,7 +4118,7 @@ export const schemaDict = { main: { type: 'procedure', description: - 'Notify a crawling service of a recent update; often when a long break between updates causes the connection with the crawling service to break.', + 'Notify a crawling service of a recent update, and that crawling should resume. Intended use is after a gap between repo stream events caused the crawling service to disconnect. Does not require auth; implemented by Relay.', input: { encoding: 'application/json', schema: { @@ -4058,7 +4128,7 @@ export const schemaDict = { hostname: { type: 'string', description: - 'Hostname of the service that is notifying of update.', + 'Hostname of the current service (usually a PDS) that is notifying of update.', }, }, }, @@ -4072,7 +4142,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Request a service to persistently crawl hosted repos.', + description: + 'Request a service to persistently crawl hosted repos. Expected use is new PDS instances declaring their existence to Relays. Does not require auth.', input: { encoding: 'application/json', schema: { @@ -4082,7 +4153,7 @@ export const schemaDict = { hostname: { type: 'string', description: - 'Hostname of the service that is requesting to be crawled.', + 'Hostname of the current service (eg, PDS) that is requesting to be crawled.', }, }, }, @@ -4096,13 +4167,14 @@ export const schemaDict = { defs: { main: { type: 'subscription', - description: 'Subscribe to repo updates.', + description: + 'Repository event stream, aka Firehose endpoint. Outputs repo commits with diff data, and identity update events, for all repositories on the current server. See the atproto specifications for details around stream sequencing, repo versioning, CAR diff format, and more. Public and does not require auth; implemented by PDS and Relay.', parameters: { type: 'params', properties: { cursor: { type: 'integer', - description: 'The last known event to backfill from.', + description: 'The last known event seq number to backfill from.', }, }, }, @@ -4124,11 +4196,15 @@ export const schemaDict = { }, { name: 'ConsumerTooSlow', + description: + 'If the consumer of the stream can not keep up with events, and a backlog gets too large, the server will drop the connection.', }, ], }, commit: { type: 'object', + description: + 'Represents an update of repository state. Note that empty commits are allowed, which include no repo data changes, but an update to rev and signature.', required: [ 'seq', 'rebase', @@ -4146,34 +4222,45 @@ export const schemaDict = { properties: { seq: { type: 'integer', + description: 'The stream sequence number of this message.', }, rebase: { type: 'boolean', + description: 'DEPRECATED -- unused', }, tooBig: { type: 'boolean', + description: + 'Indicates that this commit contained too many ops, or data size was too large. Consumers will need to make a separate request to get missing data.', }, repo: { type: 'string', format: 'did', + description: 'The repo this event comes from.', }, commit: { type: 'cid-link', + description: 'Repo commit object CID.', }, prev: { type: 'cid-link', + description: + 'DEPRECATED -- unused. WARNING -- nullable and optional; stick with optional to ensure golang interoperability.', }, rev: { type: 'string', - description: 'The rev of the emitted commit.', + description: + 'The rev of the emitted commit. Note that this information is also in the commit object included in blocks, unless this is a tooBig event.', }, since: { type: 'string', - description: 'The rev of the last emitted commit from this repo.', + description: + 'The rev of the last emitted commit from this repo (if any).', }, blocks: { type: 'bytes', - description: 'CAR file containing relevant blocks.', + description: + 'CAR file containing relevant blocks, as a diff since the previous repo state.', maxLength: 1000000, }, ops: { @@ -4181,6 +4268,8 @@ export const schemaDict = { items: { type: 'ref', ref: 'lex:com.atproto.sync.subscribeRepos#repoOp', + description: + 'List of repo mutation operations in this commit (eg, records created, updated, or deleted).', }, maxLength: 200, }, @@ -4188,16 +4277,22 @@ export const schemaDict = { type: 'array', items: { type: 'cid-link', + description: + 'List of new blobs (by CID) referenced by records in this commit.', }, }, time: { type: 'string', format: 'datetime', + description: + 'Timestamp of when this message was originally broadcast.', }, }, }, handle: { type: 'object', + description: + "Represents an update of the account's handle, or transition to/from invalid state.", required: ['seq', 'did', 'handle', 'time'], properties: { seq: { @@ -4219,6 +4314,8 @@ export const schemaDict = { }, migrate: { type: 'object', + description: + 'Represents an account moving from one PDS instance to another. NOTE: not implemented; full account migration may introduce a new message instead.', required: ['seq', 'did', 'migrateTo', 'time'], nullable: ['migrateTo'], properties: { @@ -4240,6 +4337,7 @@ export const schemaDict = { }, tombstone: { type: 'object', + description: 'Indicates that an account has been deleted.', required: ['seq', 'did', 'time'], properties: { seq: { @@ -4270,8 +4368,7 @@ export const schemaDict = { }, repoOp: { type: 'object', - description: - "A repo operation, ie a write of a single record. For creates and updates, CID is the record's CID as of this operation. For deletes, it's null.", + description: 'A repo operation, ie a mutation of a single record.', required: ['action', 'path', 'cid'], nullable: ['cid'], properties: { @@ -4284,6 +4381,8 @@ export const schemaDict = { }, cid: { type: 'cid-link', + description: + 'For creates and updates, the new record CID. For deletions, null.', }, }, }, @@ -4324,7 +4423,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Fetch all labels from a labeler created after a certain date.', + 'Fetch all labels from a labeler created after a certain date. DEPRECATED: use queryLabels or subscribeLabels instead', parameters: { type: 'params', properties: { @@ -4440,7 +4539,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Transfer an account.', + description: + 'Transfer an account. NOTE: temporary method, necessarily how account migration will be implemented.', input: { encoding: 'application/json', schema: { @@ -4513,7 +4613,6 @@ export const schemaDict = { AppBskyActorDefs: { lexicon: 1, id: 'app.bsky.actor.defs', - description: 'A reference to an actor in the network.', defs: { profileViewBasic: { type: 'object', @@ -4646,6 +4745,8 @@ export const schemaDict = { }, viewerState: { type: 'object', + description: + "Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests.", properties: { muted: { type: 'boolean', @@ -4815,7 +4916,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get private preferences attached to the account.', + description: + 'Get private preferences attached to the current account. Expected use is synchronization between multiple devices, and import/export during account migration. Requires auth.', parameters: { type: 'params', properties: {}, @@ -4842,7 +4944,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get detailed profile view of an actor.', + description: + 'Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth.', parameters: { type: 'params', required: ['actor'], @@ -4850,6 +4953,7 @@ export const schemaDict = { actor: { type: 'string', format: 'at-identifier', + description: 'Handle or DID of account to fetch profile of.', }, }, }, @@ -4909,7 +5013,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of suggested actors, used for discovery.', + description: + 'Get a list of suggested actors. Expected use is discovery of accounts to follow during new account onboarding.', parameters: { type: 'params', properties: { @@ -4952,7 +5057,7 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a profile.', + description: 'A declaration of a Bluesky account profile.', key: 'literal:self', record: { type: 'object', @@ -4964,21 +5069,28 @@ export const schemaDict = { }, description: { type: 'string', + description: 'Free-form profile description text.', maxGraphemes: 256, maxLength: 2560, }, avatar: { type: 'blob', + description: + "Small image to be displayed next to posts from account. AKA, 'profile picture'", accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, banner: { type: 'blob', + description: + 'Larger horizontal image to display behind profile view.', accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, labels: { type: 'union', + description: + 'Self-label values, specific to the Bluesky application, on the overall account.', refs: ['lex:com.atproto.label.defs#selfLabels'], }, }, @@ -5015,7 +5127,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Find actors (profiles) matching search criteria.', + description: + 'Find actors (profiles) matching search criteria. Does not require auth.', parameters: { type: 'params', properties: { @@ -5067,7 +5180,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Find actor suggestions for a prefix search term.', + description: + 'Find actor suggestions for a prefix search term. Expected use is for auto-completion during text field entry. Does not require auth.', parameters: { type: 'params', properties: { @@ -5109,11 +5223,11 @@ export const schemaDict = { AppBskyEmbedExternal: { lexicon: 1, id: 'app.bsky.embed.external', - description: - 'A representation of some externally linked content, embedded in another form of content.', defs: { main: { type: 'object', + description: + "A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post).", required: ['external'], properties: { external: { @@ -5177,7 +5291,7 @@ export const schemaDict = { AppBskyEmbedImages: { lexicon: 1, id: 'app.bsky.embed.images', - description: 'A set of images embedded in some other form of content.', + description: 'A set of images embedded in a Bluesky record (eg, a post).', defs: { main: { type: 'object', @@ -5204,6 +5318,8 @@ export const schemaDict = { }, alt: { type: 'string', + description: + 'Alt text description of the image, for accessibility.', }, aspectRatio: { type: 'ref', @@ -5247,12 +5363,18 @@ export const schemaDict = { properties: { thumb: { type: 'string', + description: + 'Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View.', }, fullsize: { type: 'string', + description: + 'Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View.', }, alt: { type: 'string', + description: + 'Alt text description of the image, for accessibility.', }, aspectRatio: { type: 'ref', @@ -5266,7 +5388,7 @@ export const schemaDict = { lexicon: 1, id: 'app.bsky.embed.record', description: - 'A representation of a record embedded in another form of content.', + 'A representation of a record embedded in a Bluesky record (eg, a post). For example, a quote-post, or sharing a feed generator record.', defs: { main: { type: 'object', @@ -5312,6 +5434,7 @@ export const schemaDict = { }, value: { type: 'unknown', + description: 'The record data itself.', }, labels: { type: 'array', @@ -5376,7 +5499,7 @@ export const schemaDict = { lexicon: 1, id: 'app.bsky.embed.recordWithMedia', description: - 'A representation of a record embedded in another form of content, alongside other compatible embeds.', + 'A representation of a record embedded in a Bluesky record (eg, a post), alongside other compatible embeds. For example, a quote post and image, or a quote post and external URL card.', defs: { main: { type: 'object', @@ -5475,6 +5598,8 @@ export const schemaDict = { }, viewerState: { type: 'object', + description: + "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests.", properties: { repost: { type: 'string', @@ -5735,7 +5860,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Get information about a feed generator, including policies and offered feed URIs.', + 'Get information about a feed generator, including policies and offered feed URIs. Does not require auth; implemented by Feed Generator services (not App View).', output: { encoding: 'application/json', schema: { @@ -5790,7 +5915,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of the existence of a feed generator.', + description: + 'Record declaring of the existence of a feed generator, and containing metadata about it. The record can exist in any repository.', key: 'any', record: { type: 'object', @@ -5824,6 +5950,7 @@ export const schemaDict = { }, labels: { type: 'union', + description: 'Self-label values', refs: ['lex:com.atproto.label.defs#selfLabels'], }, createdAt: { @@ -5841,7 +5968,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of feeds created by the actor.', + description: + "Get a list of feeds (feed generator records) created by the actor (in the actor's repo).", parameters: { type: 'params', required: ['actor'], @@ -5889,7 +6017,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of posts liked by an actor.', + description: + 'Get a list of posts liked by an actor. Does not require auth.', parameters: { type: 'params', required: ['actor'], @@ -5945,7 +6074,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: "Get a view of an actor's feed.", + description: + "Get a view of an actor's 'author feed' (post and reposts by the author). Does not require auth.", parameters: { type: 'params', required: ['actor'], @@ -5965,6 +6095,8 @@ export const schemaDict = { }, filter: { type: 'string', + description: + 'Combinations of post/repost types to include in response.', knownValues: [ 'posts_with_replies', 'posts_no_replies', @@ -6012,7 +6144,7 @@ export const schemaDict = { main: { type: 'query', description: - "Get a hydrated feed from an actor's selected feed generator.", + "Get a hydrated feed from an actor's selected feed generator. Implemented by App View.", parameters: { type: 'params', required: ['feed'], @@ -6065,7 +6197,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get information about a feed generator.', + description: + 'Get information about a feed generator. Implemented by AppView.', parameters: { type: 'params', required: ['feed'], @@ -6073,6 +6206,7 @@ export const schemaDict = { feed: { type: 'string', format: 'at-uri', + description: 'AT-URI of the feed generator record.', }, }, }, @@ -6088,9 +6222,13 @@ export const schemaDict = { }, isOnline: { type: 'boolean', + description: + 'Indicates whether the feed generator service has been online recently, or else seems to be inactive.', }, isValid: { type: 'boolean', + description: + 'Indicates whether the feed generator service is compatible with the record declaration.', }, }, }, @@ -6143,7 +6281,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a skeleton of a feed provided by a feed generator.', + description: + 'Get a skeleton of a feed provided by a feed generator. Auth is optional, depending on provider requirements, and provides the DID of the requester. Implemented by Feed Generator Service.', parameters: { type: 'params', required: ['feed'], @@ -6151,6 +6290,8 @@ export const schemaDict = { feed: { type: 'string', format: 'at-uri', + description: + 'Reference to feed generator record describing the specific feed being requested.', }, limit: { type: 'integer', @@ -6196,7 +6337,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get the list of likes.', + description: + 'Get like records which reference a subject (by AT-URI and CID).', parameters: { type: 'params', required: ['uri'], @@ -6204,10 +6346,13 @@ export const schemaDict = { uri: { type: 'string', format: 'at-uri', + description: 'AT-URI of the subject (eg, a post record).', }, cid: { type: 'string', format: 'cid', + description: + 'CID of the subject record (aka, specific version of record), to filter likes.', }, limit: { type: 'integer', @@ -6274,7 +6419,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a view of a recent posts from actors in a list.', + description: + 'Get a feed of recent posts from a list (posts and reposts from any actors on the list). Does not require auth.', parameters: { type: 'params', required: ['list'], @@ -6282,6 +6428,7 @@ export const schemaDict = { list: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) to the list record.', }, limit: { type: 'integer', @@ -6327,7 +6474,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get posts in a thread.', + description: + 'Get posts in a thread. Does not require auth, but additional metadata and filtering will be applied for authed requests.', parameters: { type: 'params', required: ['uri'], @@ -6335,15 +6483,20 @@ export const schemaDict = { uri: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) to post record.', }, depth: { type: 'integer', + description: + 'How many levels of reply depth should be included in response.', default: 6, minimum: 0, maximum: 1000, }, parentHeight: { type: 'integer', + description: + 'How many levels of parent (and grandparent, etc) post to include.', default: 80, minimum: 0, maximum: 1000, @@ -6381,13 +6534,15 @@ export const schemaDict = { defs: { main: { type: 'query', - description: "Get a view of an actor's feed.", + description: + "Gets post views for a specified list of posts (by AT-URI). This is sometimes referred to as 'hydrating' a 'feed skeleton'.", parameters: { type: 'params', required: ['uris'], properties: { uris: { type: 'array', + description: 'List of post AT-URIs to return hydrated views for.', items: { type: 'string', format: 'at-uri', @@ -6421,7 +6576,7 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of reposts.', + description: 'Get a list of reposts for a given post.', parameters: { type: 'params', required: ['uri'], @@ -6429,10 +6584,13 @@ export const schemaDict = { uri: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) of post record', }, cid: { type: 'string', format: 'cid', + description: + 'If supplied, filters to reposts of specific version (by CID) of the post record.', }, limit: { type: 'integer', @@ -6481,7 +6639,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of suggested feeds for the viewer.', + description: + 'Get a list of suggested feeds (feed generators) for the requesting account.', parameters: { type: 'params', properties: { @@ -6524,12 +6683,15 @@ export const schemaDict = { defs: { main: { type: 'query', - description: "Get a view of the actor's home timeline.", + description: + "Get a view of the requesting account's home timeline. This is expected to be some form of reverse-chronological feed.", parameters: { type: 'params', properties: { algorithm: { type: 'string', + description: + "Variant 'algorithm' for timeline. Implementation-specific. NOTE: most feed flexibility has been moved to feed generator mechanism.", }, limit: { type: 'integer', @@ -6570,7 +6732,7 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a like.', + description: "Record declaring a 'like' of a piece of subject content.", key: 'tid', record: { type: 'object', @@ -6595,7 +6757,7 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a post.', + description: 'Record containing a Bluesky post.', key: 'tid', record: { type: 'object', @@ -6605,10 +6767,12 @@ export const schemaDict = { type: 'string', maxLength: 3000, maxGraphemes: 300, + description: + 'The primary post content. May be an empty string, if there are embeds.', }, entities: { type: 'array', - description: 'Deprecated: replaced by app.bsky.richtext.facet.', + description: 'DEPRECATED: replaced by app.bsky.richtext.facet.', items: { type: 'ref', ref: 'lex:app.bsky.feed.post#entity', @@ -6616,6 +6780,8 @@ export const schemaDict = { }, facets: { type: 'array', + description: + 'Annotations of text (mentions, URLs, hashtags, etc)', items: { type: 'ref', ref: 'lex:app.bsky.richtext.facet', @@ -6636,6 +6802,8 @@ export const schemaDict = { }, langs: { type: 'array', + description: + 'Indicates human language of post primary text content.', maxLength: 3, items: { type: 'string', @@ -6644,21 +6812,25 @@ export const schemaDict = { }, labels: { type: 'union', + description: + 'Self-label values for this post. Effectively content warnings.', refs: ['lex:com.atproto.label.defs#selfLabels'], }, tags: { type: 'array', + description: 'Additional non-inline tags describing this post.', maxLength: 8, items: { type: 'string', maxLength: 640, maxGraphemes: 64, }, - description: 'Additional non-inline tags describing this post.', }, createdAt: { type: 'string', format: 'datetime', + description: + 'Client-declared timestamp when this post was originally created.', }, }, }, @@ -6718,7 +6890,8 @@ export const schemaDict = { id: 'app.bsky.feed.repost', defs: { main: { - description: 'A declaration of a repost.', + description: + "Record representing a 'repost' of an existing Bluesky post.", type: 'record', key: 'tid', record: { @@ -6744,7 +6917,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Find posts matching search criteria.', + description: + 'Find posts matching search criteria, returning views of those posts.', parameters: { type: 'params', required: ['q'], @@ -6807,7 +6981,7 @@ export const schemaDict = { type: 'record', key: 'tid', description: - "Defines interaction gating rules for a thread. The rkey of the threadgate record should match the rkey of the thread's root post.", + "Record defining interaction gating rules for a thread (aka, reply controls). The record key (rkey) of the threadgate record must match the record key of the thread's root post, and that record must be in the same repository..", record: { type: 'object', required: ['post', 'createdAt'], @@ -6815,6 +6989,7 @@ export const schemaDict = { post: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) to the post record.', }, allow: { type: 'array', @@ -6864,7 +7039,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a block.', + description: + "Record declaring a 'block' relationship against another account. NOTE: blocks are public in Bluesky; see blog posts for details.", key: 'tid', record: { type: 'object', @@ -6873,6 +7049,7 @@ export const schemaDict = { subject: { type: 'string', format: 'did', + description: 'DID of the account to be blocked.', }, createdAt: { type: 'string', @@ -7061,7 +7238,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a social follow.', + description: + "Record declaring a social 'follow' relationship of another account. Duplicate follows will be ignored by the AppView.", key: 'tid', record: { type: 'object', @@ -7086,7 +7264,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of who the actor is blocking.', + description: + 'Enumerates which accounts the requesting account is currently blocking. Requires auth.', parameters: { type: 'params', properties: { @@ -7129,7 +7308,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: "Get a list of an actor's followers.", + description: + 'Enumerates accounts which follow a specified account (actor).', parameters: { type: 'params', required: ['actor'], @@ -7181,7 +7361,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of who the actor follows.', + description: + 'Enumerates accounts which a specified account (actor) follows.', parameters: { type: 'params', required: ['actor'], @@ -7233,7 +7414,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of actors.', + description: + "Gets a 'view' (with additional context) of a specified list.", parameters: { type: 'params', required: ['list'], @@ -7241,6 +7423,7 @@ export const schemaDict = { list: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) of the list record to hydrate.', }, limit: { type: 'integer', @@ -7285,7 +7468,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get lists that the actor is blocking.', + description: + 'Get mod lists that the requesting account (actor) is blocking. Requires auth.', parameters: { type: 'params', properties: { @@ -7328,7 +7512,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get lists that the actor is muting.', + description: + 'Enumerates mod lists that the requesting account (actor) currently has muted. Requires auth.', parameters: { type: 'params', properties: { @@ -7371,7 +7556,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of lists that belong to an actor.', + description: + 'Enumerates the lists created by a specified account (actor).', parameters: { type: 'params', required: ['actor'], @@ -7379,6 +7565,7 @@ export const schemaDict = { actor: { type: 'string', format: 'at-identifier', + description: 'The account (actor) to enumerate lists from.', }, limit: { type: 'integer', @@ -7419,7 +7606,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of who the actor mutes.', + description: + 'Enumerates accounts that the requesting account (actor) currently has muted. Requires auth.', parameters: { type: 'params', properties: { @@ -7463,7 +7651,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Enumerates public relationships between one account, and a list of other accounts', + 'Enumerates public relationships between one account, and a list of other accounts. Does not require auth.', parameters: { type: 'params', required: ['actor'], @@ -7471,9 +7659,12 @@ export const schemaDict = { actor: { type: 'string', format: 'at-identifier', + description: 'Primary account requesting relationships for.', }, others: { type: 'array', + description: + "List of 'other' accounts to be related back to the primary.", maxLength: 30, items: { type: 'string', @@ -7521,7 +7712,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get suggested follows related to a given actor.', + description: + 'Enumerates follows similar to a given account (actor). Expected use is to recommend additional accounts immediately after following one account.', parameters: { type: 'params', required: ['actor'], @@ -7557,7 +7749,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a list of actors.', + description: + 'Record representing a list of accounts (actors). Scope includes both moderation-oriented lists and curration-oriented lists.', key: 'tid', record: { type: 'object', @@ -7565,12 +7758,15 @@ export const schemaDict = { properties: { purpose: { type: 'ref', + description: + 'Defines the purpose of the list (aka, moderation-oriented or curration-oriented)', ref: 'lex:app.bsky.graph.defs#listPurpose', }, name: { type: 'string', maxLength: 64, minLength: 1, + description: 'Display name for list; can not be empty.', }, description: { type: 'string', @@ -7608,7 +7804,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A block of an entire list of actors.', + description: + 'Record representing a block relationship against an entire an entire list of accounts (actors).', key: 'tid', record: { type: 'object', @@ -7617,6 +7814,7 @@ export const schemaDict = { subject: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) to the mod list record.', }, createdAt: { type: 'string', @@ -7633,7 +7831,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'An item under a declared list of actors.', + description: + "Record representing an account's inclusion on a specific list. The AppView will ignore duplicate listitem records.", key: 'tid', record: { type: 'object', @@ -7642,10 +7841,13 @@ export const schemaDict = { subject: { type: 'string', format: 'did', + description: 'The account which is included on the list.', }, list: { type: 'string', format: 'at-uri', + description: + 'Reference (AT-URI) to the list record (app.bsky.graph.list).', }, createdAt: { type: 'string', @@ -7662,7 +7864,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Mute an actor by DID or handle.', + description: + 'Creates a mute relationship for the specified account. Mutes are private in Bluesky. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7685,7 +7888,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Mute a list of actors.', + description: + 'Creates a mute relationship for the specified list of accounts. Mutes are private in Bluesky. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7708,7 +7912,7 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Unmute an actor by DID or handle.', + description: 'Unmutes the specified account. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7731,7 +7935,7 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Unmute a list of actors.', + description: 'Unmutes the specified list of accounts. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7754,7 +7958,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get the count of unread notifications.', + description: + 'Count the number of unread notifications for the requesting account. Requires auth.', parameters: { type: 'params', properties: { @@ -7785,7 +7990,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of notifications.', + description: + 'Enumerate notifications for the requesting account. Requires auth.', parameters: { type: 'params', properties: { @@ -7896,7 +8102,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Register for push notifications with a service.', + description: + 'Register to receive push notifications, via a specified service, for the requesting account. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7929,7 +8136,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Notify server that the user has seen notifications.', + description: + 'Notify server that the requesting account has seen notifications. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7952,6 +8160,7 @@ export const schemaDict = { defs: { main: { type: 'object', + description: 'Annotation of a sub-string within rich text.', required: ['index', 'features'], properties: { index: { @@ -7973,7 +8182,8 @@ export const schemaDict = { }, mention: { type: 'object', - description: 'A facet feature for actor mentions.', + description: + "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID.", required: ['did'], properties: { did: { @@ -7984,7 +8194,8 @@ export const schemaDict = { }, link: { type: 'object', - description: 'A facet feature for links.', + description: + 'Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL.', required: ['uri'], properties: { uri: { @@ -7995,7 +8206,8 @@ export const schemaDict = { }, tag: { type: 'object', - description: 'A hashtag.', + description: + "Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags').", required: ['tag'], properties: { tag: { @@ -8008,7 +8220,7 @@ export const schemaDict = { byteSlice: { type: 'object', description: - 'A text segment. Start is inclusive, end is exclusive. Indices are for utf8-encoded strings.', + 'Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets.', required: ['byteStart', 'byteEnd'], properties: { byteStart: { diff --git a/packages/api/src/client/types/app/bsky/actor/defs.ts b/packages/api/src/client/types/app/bsky/actor/defs.ts index 5c1791e6130..2e58d890b72 100644 --- a/packages/api/src/client/types/app/bsky/actor/defs.ts +++ b/packages/api/src/client/types/app/bsky/actor/defs.ts @@ -82,6 +82,7 @@ export function validateProfileViewDetailed(v: unknown): ValidationResult { return lexicons.validate('app.bsky.actor.defs#profileViewDetailed', v) } +/** Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests. */ export interface ViewerState { muted?: boolean mutedByList?: AppBskyGraphDefs.ListViewBasic diff --git a/packages/api/src/client/types/app/bsky/actor/getProfile.ts b/packages/api/src/client/types/app/bsky/actor/getProfile.ts index 47e36fe974a..bbd88c30a7b 100644 --- a/packages/api/src/client/types/app/bsky/actor/getProfile.ts +++ b/packages/api/src/client/types/app/bsky/actor/getProfile.ts @@ -9,6 +9,7 @@ import { CID } from 'multiformats/cid' import * as AppBskyActorDefs from './defs' export interface QueryParams { + /** Handle or DID of account to fetch profile of. */ actor: string } diff --git a/packages/api/src/client/types/app/bsky/actor/profile.ts b/packages/api/src/client/types/app/bsky/actor/profile.ts index fa36f4298f1..a0c51e060c5 100644 --- a/packages/api/src/client/types/app/bsky/actor/profile.ts +++ b/packages/api/src/client/types/app/bsky/actor/profile.ts @@ -9,8 +9,11 @@ import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface Record { displayName?: string + /** Free-form profile description text. */ description?: string + /** Small image to be displayed next to posts from account. AKA, 'profile picture' */ avatar?: BlobRef + /** Larger horizontal image to display behind profile view. */ banner?: BlobRef labels?: | ComAtprotoLabelDefs.SelfLabels diff --git a/packages/api/src/client/types/app/bsky/embed/external.ts b/packages/api/src/client/types/app/bsky/embed/external.ts index 271c103dbba..5832cbb3987 100644 --- a/packages/api/src/client/types/app/bsky/embed/external.ts +++ b/packages/api/src/client/types/app/bsky/embed/external.ts @@ -6,6 +6,7 @@ import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +/** A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post). */ export interface Main { external: External [k: string]: unknown diff --git a/packages/api/src/client/types/app/bsky/embed/images.ts b/packages/api/src/client/types/app/bsky/embed/images.ts index 77909a4b3b0..ddfdf4c156c 100644 --- a/packages/api/src/client/types/app/bsky/embed/images.ts +++ b/packages/api/src/client/types/app/bsky/embed/images.ts @@ -26,6 +26,7 @@ export function validateMain(v: unknown): ValidationResult { export interface Image { image: BlobRef + /** Alt text description of the image, for accessibility. */ alt: string aspectRatio?: AspectRatio [k: string]: unknown @@ -76,8 +77,11 @@ export function validateView(v: unknown): ValidationResult { } export interface ViewImage { + /** Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View. */ thumb: string + /** Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View. */ fullsize: string + /** Alt text description of the image, for accessibility. */ alt: string aspectRatio?: AspectRatio [k: string]: unknown diff --git a/packages/api/src/client/types/app/bsky/embed/record.ts b/packages/api/src/client/types/app/bsky/embed/record.ts index caee8f08cdd..388687fd665 100644 --- a/packages/api/src/client/types/app/bsky/embed/record.ts +++ b/packages/api/src/client/types/app/bsky/embed/record.ts @@ -57,6 +57,7 @@ export interface ViewRecord { uri: string cid: string author: AppBskyActorDefs.ProfileViewBasic + /** The record data itself. */ value: {} labels?: ComAtprotoLabelDefs.Label[] embeds?: ( diff --git a/packages/api/src/client/types/app/bsky/feed/defs.ts b/packages/api/src/client/types/app/bsky/feed/defs.ts index 82cbfd9951a..949b8fb975e 100644 --- a/packages/api/src/client/types/app/bsky/feed/defs.ts +++ b/packages/api/src/client/types/app/bsky/feed/defs.ts @@ -45,6 +45,7 @@ export function validatePostView(v: unknown): ValidationResult { return lexicons.validate('app.bsky.feed.defs#postView', v) } +/** Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests. */ export interface ViewerState { repost?: string like?: string diff --git a/packages/api/src/client/types/app/bsky/feed/getAuthorFeed.ts b/packages/api/src/client/types/app/bsky/feed/getAuthorFeed.ts index a070dad6ff7..3f498e49514 100644 --- a/packages/api/src/client/types/app/bsky/feed/getAuthorFeed.ts +++ b/packages/api/src/client/types/app/bsky/feed/getAuthorFeed.ts @@ -12,6 +12,7 @@ export interface QueryParams { actor: string limit?: number cursor?: string + /** Combinations of post/repost types to include in response. */ filter?: | 'posts_with_replies' | 'posts_no_replies' diff --git a/packages/api/src/client/types/app/bsky/feed/getFeedGenerator.ts b/packages/api/src/client/types/app/bsky/feed/getFeedGenerator.ts index a2f9b405c97..f08c9b59340 100644 --- a/packages/api/src/client/types/app/bsky/feed/getFeedGenerator.ts +++ b/packages/api/src/client/types/app/bsky/feed/getFeedGenerator.ts @@ -9,6 +9,7 @@ import { CID } from 'multiformats/cid' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** AT-URI of the feed generator record. */ feed: string } @@ -16,7 +17,9 @@ export type InputSchema = undefined export interface OutputSchema { view: AppBskyFeedDefs.GeneratorView + /** Indicates whether the feed generator service has been online recently, or else seems to be inactive. */ isOnline: boolean + /** Indicates whether the feed generator service is compatible with the record declaration. */ isValid: boolean [k: string]: unknown } diff --git a/packages/api/src/client/types/app/bsky/feed/getFeedSkeleton.ts b/packages/api/src/client/types/app/bsky/feed/getFeedSkeleton.ts index 0aa325d7fec..1426469c84d 100644 --- a/packages/api/src/client/types/app/bsky/feed/getFeedSkeleton.ts +++ b/packages/api/src/client/types/app/bsky/feed/getFeedSkeleton.ts @@ -9,6 +9,7 @@ import { CID } from 'multiformats/cid' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** Reference to feed generator record describing the specific feed being requested. */ feed: string limit?: number cursor?: string diff --git a/packages/api/src/client/types/app/bsky/feed/getLikes.ts b/packages/api/src/client/types/app/bsky/feed/getLikes.ts index d78047feb6e..9725ef065d9 100644 --- a/packages/api/src/client/types/app/bsky/feed/getLikes.ts +++ b/packages/api/src/client/types/app/bsky/feed/getLikes.ts @@ -9,7 +9,9 @@ import { CID } from 'multiformats/cid' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { + /** AT-URI of the subject (eg, a post record). */ uri: string + /** CID of the subject record (aka, specific version of record), to filter likes. */ cid?: string limit?: number cursor?: string diff --git a/packages/api/src/client/types/app/bsky/feed/getListFeed.ts b/packages/api/src/client/types/app/bsky/feed/getListFeed.ts index 511e9526c6d..6b4156ddda9 100644 --- a/packages/api/src/client/types/app/bsky/feed/getListFeed.ts +++ b/packages/api/src/client/types/app/bsky/feed/getListFeed.ts @@ -9,6 +9,7 @@ import { CID } from 'multiformats/cid' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** Reference (AT-URI) to the list record. */ list: string limit?: number cursor?: string diff --git a/packages/api/src/client/types/app/bsky/feed/getPostThread.ts b/packages/api/src/client/types/app/bsky/feed/getPostThread.ts index d3865db9ee2..d03ad7de127 100644 --- a/packages/api/src/client/types/app/bsky/feed/getPostThread.ts +++ b/packages/api/src/client/types/app/bsky/feed/getPostThread.ts @@ -9,8 +9,11 @@ import { CID } from 'multiformats/cid' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** Reference (AT-URI) to post record. */ uri: string + /** How many levels of reply depth should be included in response. */ depth?: number + /** How many levels of parent (and grandparent, etc) post to include. */ parentHeight?: number } diff --git a/packages/api/src/client/types/app/bsky/feed/getPosts.ts b/packages/api/src/client/types/app/bsky/feed/getPosts.ts index 933919bdcc1..cd932d88047 100644 --- a/packages/api/src/client/types/app/bsky/feed/getPosts.ts +++ b/packages/api/src/client/types/app/bsky/feed/getPosts.ts @@ -9,6 +9,7 @@ import { CID } from 'multiformats/cid' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** List of post AT-URIs to return hydrated views for. */ uris: string[] } diff --git a/packages/api/src/client/types/app/bsky/feed/getRepostedBy.ts b/packages/api/src/client/types/app/bsky/feed/getRepostedBy.ts index 30a1a109aaa..d27aa1dec0a 100644 --- a/packages/api/src/client/types/app/bsky/feed/getRepostedBy.ts +++ b/packages/api/src/client/types/app/bsky/feed/getRepostedBy.ts @@ -9,7 +9,9 @@ import { CID } from 'multiformats/cid' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { + /** Reference (AT-URI) of post record */ uri: string + /** If supplied, filters to reposts of specific version (by CID) of the post record. */ cid?: string limit?: number cursor?: string diff --git a/packages/api/src/client/types/app/bsky/feed/getTimeline.ts b/packages/api/src/client/types/app/bsky/feed/getTimeline.ts index 6d8dacff99a..5ab2c7c4b1f 100644 --- a/packages/api/src/client/types/app/bsky/feed/getTimeline.ts +++ b/packages/api/src/client/types/app/bsky/feed/getTimeline.ts @@ -9,6 +9,7 @@ import { CID } from 'multiformats/cid' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** Variant 'algorithm' for timeline. Implementation-specific. NOTE: most feed flexibility has been moved to feed generator mechanism. */ algorithm?: string limit?: number cursor?: string diff --git a/packages/api/src/client/types/app/bsky/feed/post.ts b/packages/api/src/client/types/app/bsky/feed/post.ts index a3299e19035..401510f9ef9 100644 --- a/packages/api/src/client/types/app/bsky/feed/post.ts +++ b/packages/api/src/client/types/app/bsky/feed/post.ts @@ -14,9 +14,11 @@ import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' export interface Record { + /** The primary post content. May be an empty string, if there are embeds. */ text: string - /** Deprecated: replaced by app.bsky.richtext.facet. */ + /** DEPRECATED: replaced by app.bsky.richtext.facet. */ entities?: Entity[] + /** Annotations of text (mentions, URLs, hashtags, etc) */ facets?: AppBskyRichtextFacet.Main[] reply?: ReplyRef embed?: @@ -25,12 +27,14 @@ export interface Record { | AppBskyEmbedRecord.Main | AppBskyEmbedRecordWithMedia.Main | { $type: string; [k: string]: unknown } + /** Indicates human language of post primary text content. */ langs?: string[] labels?: | ComAtprotoLabelDefs.SelfLabels | { $type: string; [k: string]: unknown } /** Additional non-inline tags describing this post. */ tags?: string[] + /** Client-declared timestamp when this post was originally created. */ createdAt: string [k: string]: unknown } diff --git a/packages/api/src/client/types/app/bsky/feed/threadgate.ts b/packages/api/src/client/types/app/bsky/feed/threadgate.ts index a1afec85673..cc8c05a78ec 100644 --- a/packages/api/src/client/types/app/bsky/feed/threadgate.ts +++ b/packages/api/src/client/types/app/bsky/feed/threadgate.ts @@ -7,6 +7,7 @@ import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' export interface Record { + /** Reference (AT-URI) to the post record. */ post: string allow?: ( | MentionRule diff --git a/packages/api/src/client/types/app/bsky/graph/block.ts b/packages/api/src/client/types/app/bsky/graph/block.ts index c35258d979a..f2455fc08a2 100644 --- a/packages/api/src/client/types/app/bsky/graph/block.ts +++ b/packages/api/src/client/types/app/bsky/graph/block.ts @@ -7,6 +7,7 @@ import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' export interface Record { + /** DID of the account to be blocked. */ subject: string createdAt: string [k: string]: unknown diff --git a/packages/api/src/client/types/app/bsky/graph/getList.ts b/packages/api/src/client/types/app/bsky/graph/getList.ts index 13ebd9d3ae6..36c4cf0aa86 100644 --- a/packages/api/src/client/types/app/bsky/graph/getList.ts +++ b/packages/api/src/client/types/app/bsky/graph/getList.ts @@ -9,6 +9,7 @@ import { CID } from 'multiformats/cid' import * as AppBskyGraphDefs from './defs' export interface QueryParams { + /** Reference (AT-URI) of the list record to hydrate. */ list: string limit?: number cursor?: string diff --git a/packages/api/src/client/types/app/bsky/graph/getLists.ts b/packages/api/src/client/types/app/bsky/graph/getLists.ts index 80a7edfb759..644aeea3b4b 100644 --- a/packages/api/src/client/types/app/bsky/graph/getLists.ts +++ b/packages/api/src/client/types/app/bsky/graph/getLists.ts @@ -9,6 +9,7 @@ import { CID } from 'multiformats/cid' import * as AppBskyGraphDefs from './defs' export interface QueryParams { + /** The account (actor) to enumerate lists from. */ actor: string limit?: number cursor?: string diff --git a/packages/api/src/client/types/app/bsky/graph/getRelationships.ts b/packages/api/src/client/types/app/bsky/graph/getRelationships.ts index 5fce53f635c..9aa58ad2699 100644 --- a/packages/api/src/client/types/app/bsky/graph/getRelationships.ts +++ b/packages/api/src/client/types/app/bsky/graph/getRelationships.ts @@ -9,7 +9,9 @@ import { CID } from 'multiformats/cid' import * as AppBskyGraphDefs from './defs' export interface QueryParams { + /** Primary account requesting relationships for. */ actor: string + /** List of 'other' accounts to be related back to the primary. */ others?: string[] } diff --git a/packages/api/src/client/types/app/bsky/graph/list.ts b/packages/api/src/client/types/app/bsky/graph/list.ts index 4fe6dd8ed8b..fec652ccb12 100644 --- a/packages/api/src/client/types/app/bsky/graph/list.ts +++ b/packages/api/src/client/types/app/bsky/graph/list.ts @@ -11,6 +11,7 @@ import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface Record { purpose: AppBskyGraphDefs.ListPurpose + /** Display name for list; can not be empty. */ name: string description?: string descriptionFacets?: AppBskyRichtextFacet.Main[] diff --git a/packages/api/src/client/types/app/bsky/graph/listblock.ts b/packages/api/src/client/types/app/bsky/graph/listblock.ts index 770dfbb0775..e0f02be268f 100644 --- a/packages/api/src/client/types/app/bsky/graph/listblock.ts +++ b/packages/api/src/client/types/app/bsky/graph/listblock.ts @@ -7,6 +7,7 @@ import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' export interface Record { + /** Reference (AT-URI) to the mod list record. */ subject: string createdAt: string [k: string]: unknown diff --git a/packages/api/src/client/types/app/bsky/graph/listitem.ts b/packages/api/src/client/types/app/bsky/graph/listitem.ts index 5059ef69c10..d4fb5631e84 100644 --- a/packages/api/src/client/types/app/bsky/graph/listitem.ts +++ b/packages/api/src/client/types/app/bsky/graph/listitem.ts @@ -7,7 +7,9 @@ import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' export interface Record { + /** The account which is included on the list. */ subject: string + /** Reference (AT-URI) to the list record (app.bsky.graph.list). */ list: string createdAt: string [k: string]: unknown diff --git a/packages/api/src/client/types/app/bsky/richtext/facet.ts b/packages/api/src/client/types/app/bsky/richtext/facet.ts index 96573bb06fe..836136b7dac 100644 --- a/packages/api/src/client/types/app/bsky/richtext/facet.ts +++ b/packages/api/src/client/types/app/bsky/richtext/facet.ts @@ -6,6 +6,7 @@ import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +/** Annotation of a sub-string within rich text. */ export interface Main { index: ByteSlice features: (Mention | Link | Tag | { $type: string; [k: string]: unknown })[] @@ -25,7 +26,7 @@ export function validateMain(v: unknown): ValidationResult { return lexicons.validate('app.bsky.richtext.facet#main', v) } -/** A facet feature for actor mentions. */ +/** Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID. */ export interface Mention { did: string [k: string]: unknown @@ -43,7 +44,7 @@ export function validateMention(v: unknown): ValidationResult { return lexicons.validate('app.bsky.richtext.facet#mention', v) } -/** A facet feature for links. */ +/** Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL. */ export interface Link { uri: string [k: string]: unknown @@ -61,7 +62,7 @@ export function validateLink(v: unknown): ValidationResult { return lexicons.validate('app.bsky.richtext.facet#link', v) } -/** A hashtag. */ +/** Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags'). */ export interface Tag { tag: string [k: string]: unknown @@ -77,7 +78,7 @@ export function validateTag(v: unknown): ValidationResult { return lexicons.validate('app.bsky.richtext.facet#tag', v) } -/** A text segment. Start is inclusive, end is exclusive. Indices are for utf8-encoded strings. */ +/** Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets. */ export interface ByteSlice { byteStart: number byteEnd: number diff --git a/packages/api/src/client/types/com/atproto/identity/updateHandle.ts b/packages/api/src/client/types/com/atproto/identity/updateHandle.ts index 4c01e105c28..2bd2c4c9d6a 100644 --- a/packages/api/src/client/types/com/atproto/identity/updateHandle.ts +++ b/packages/api/src/client/types/com/atproto/identity/updateHandle.ts @@ -10,6 +10,7 @@ import { CID } from 'multiformats/cid' export interface QueryParams {} export interface InputSchema { + /** The new handle. */ handle: string [k: string]: unknown } diff --git a/packages/api/src/client/types/com/atproto/moderation/createReport.ts b/packages/api/src/client/types/com/atproto/moderation/createReport.ts index 826b32ff67c..7bf3cc1a380 100644 --- a/packages/api/src/client/types/com/atproto/moderation/createReport.ts +++ b/packages/api/src/client/types/com/atproto/moderation/createReport.ts @@ -14,6 +14,7 @@ export interface QueryParams {} export interface InputSchema { reasonType: ComAtprotoModerationDefs.ReasonType + /** Additional context about the content and violation. */ reason?: string subject: | ComAtprotoAdminDefs.RepoRef diff --git a/packages/api/src/client/types/com/atproto/repo/applyWrites.ts b/packages/api/src/client/types/com/atproto/repo/applyWrites.ts index f4a8a269201..df35ec7dcbf 100644 --- a/packages/api/src/client/types/com/atproto/repo/applyWrites.ts +++ b/packages/api/src/client/types/com/atproto/repo/applyWrites.ts @@ -10,11 +10,12 @@ import { CID } from 'multiformats/cid' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ + /** The handle or DID of the repo (aka, current account). */ repo: string - /** Flag for validating the records. */ + /** Can be set to 'false' to skip Lexicon schema validation of record data, for all operations. */ validate?: boolean writes: (Create | Update | Delete)[] + /** If provided, the entire operation will fail if the current repo commit CID does not match this value. Used to prevent conflicting repo mutations. */ swapCommit?: string [k: string]: unknown } @@ -43,7 +44,7 @@ export function toKnownErr(e: any) { return e } -/** Create a new record. */ +/** Operation which creates a new record. */ export interface Create { collection: string rkey?: string @@ -63,7 +64,7 @@ export function validateCreate(v: unknown): ValidationResult { return lexicons.validate('com.atproto.repo.applyWrites#create', v) } -/** Update an existing record. */ +/** Operation which updates an existing record. */ export interface Update { collection: string rkey: string @@ -83,7 +84,7 @@ export function validateUpdate(v: unknown): ValidationResult { return lexicons.validate('com.atproto.repo.applyWrites#update', v) } -/** Delete an existing record. */ +/** Operation which deletes an existing record. */ export interface Delete { collection: string rkey: string diff --git a/packages/api/src/client/types/com/atproto/repo/createRecord.ts b/packages/api/src/client/types/com/atproto/repo/createRecord.ts index 2056778c71c..6b13f67db7f 100644 --- a/packages/api/src/client/types/com/atproto/repo/createRecord.ts +++ b/packages/api/src/client/types/com/atproto/repo/createRecord.ts @@ -10,15 +10,15 @@ import { CID } from 'multiformats/cid' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ + /** The handle or DID of the repo (aka, current account). */ repo: string /** The NSID of the record collection. */ collection: string - /** The key of the record. */ + /** The Record Key. */ rkey?: string - /** Flag for validating the record. */ + /** Can be set to 'false' to skip Lexicon schema validation of record data. */ validate?: boolean - /** The record to create. */ + /** The record itself. Must contain a $type field. */ record: {} /** Compare and swap with the previous commit by CID. */ swapCommit?: string diff --git a/packages/api/src/client/types/com/atproto/repo/deleteRecord.ts b/packages/api/src/client/types/com/atproto/repo/deleteRecord.ts index 5bf9237abbb..54109b62f31 100644 --- a/packages/api/src/client/types/com/atproto/repo/deleteRecord.ts +++ b/packages/api/src/client/types/com/atproto/repo/deleteRecord.ts @@ -10,11 +10,11 @@ import { CID } from 'multiformats/cid' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ + /** The handle or DID of the repo (aka, current account). */ repo: string /** The NSID of the record collection. */ collection: string - /** The key of the record. */ + /** The Record Key. */ rkey: string /** Compare and swap with the previous record by CID. */ swapRecord?: string diff --git a/packages/api/src/client/types/com/atproto/repo/describeRepo.ts b/packages/api/src/client/types/com/atproto/repo/describeRepo.ts index e6ecedb3297..f17a8410782 100644 --- a/packages/api/src/client/types/com/atproto/repo/describeRepo.ts +++ b/packages/api/src/client/types/com/atproto/repo/describeRepo.ts @@ -17,8 +17,11 @@ export type InputSchema = undefined export interface OutputSchema { handle: string did: string + /** The complete DID document for this account. */ didDoc: {} + /** List of all the collections (NSIDs) for which this repo contains at least one record. */ collections: string[] + /** Indicates if handle is currently valid (resolves bi-directionally) */ handleIsCorrect: boolean [k: string]: unknown } diff --git a/packages/api/src/client/types/com/atproto/repo/getRecord.ts b/packages/api/src/client/types/com/atproto/repo/getRecord.ts index 56338d016ee..a6d2bd39e8c 100644 --- a/packages/api/src/client/types/com/atproto/repo/getRecord.ts +++ b/packages/api/src/client/types/com/atproto/repo/getRecord.ts @@ -12,7 +12,7 @@ export interface QueryParams { repo: string /** The NSID of the record collection. */ collection: string - /** The key of the record. */ + /** The Record Key. */ rkey: string /** The CID of the version of the record. If not specified, then return the most recent version. */ cid?: string diff --git a/packages/api/src/client/types/com/atproto/repo/putRecord.ts b/packages/api/src/client/types/com/atproto/repo/putRecord.ts index 269ef759401..7421ee19780 100644 --- a/packages/api/src/client/types/com/atproto/repo/putRecord.ts +++ b/packages/api/src/client/types/com/atproto/repo/putRecord.ts @@ -10,17 +10,17 @@ import { CID } from 'multiformats/cid' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ + /** The handle or DID of the repo (aka, current account). */ repo: string /** The NSID of the record collection. */ collection: string - /** The key of the record. */ + /** The Record Key. */ rkey: string - /** Flag for validating the record. */ + /** Can be set to 'false' to skip Lexicon schema validation of record data. */ validate?: boolean /** The record to write. */ record: {} - /** Compare and swap with the previous record by CID. */ + /** Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation */ swapRecord?: string | null /** Compare and swap with the previous commit by CID. */ swapCommit?: string diff --git a/packages/api/src/client/types/com/atproto/server/createAccount.ts b/packages/api/src/client/types/com/atproto/server/createAccount.ts index b62adf97cb1..5e36eca0ee3 100644 --- a/packages/api/src/client/types/com/atproto/server/createAccount.ts +++ b/packages/api/src/client/types/com/atproto/server/createAccount.ts @@ -11,22 +11,30 @@ export interface QueryParams {} export interface InputSchema { email?: string + /** Requested handle for the account. */ handle: string + /** Pre-existing atproto DID, being imported to a new account. */ did?: string inviteCode?: string verificationCode?: string verificationPhone?: string + /** Initial account password. May need to meet instance-specific password strength requirements. */ password?: string + /** DID PLC rotation key (aka, recovery key) to be included in PLC creation operation. */ recoveryKey?: string + /** A signed DID PLC operation to be submitted as part of importing an existing account to this instance. NOTE: this optional field may be updated when full account migration is implemented. */ plcOp?: {} [k: string]: unknown } +/** Account login session returned on successful account creation. */ export interface OutputSchema { accessJwt: string refreshJwt: string handle: string + /** The DID of the new account. */ did: string + /** Complete DID document. */ didDoc?: {} [k: string]: unknown } diff --git a/packages/api/src/client/types/com/atproto/server/createAppPassword.ts b/packages/api/src/client/types/com/atproto/server/createAppPassword.ts index d6e9ce3ddf5..8b9a5d53a7c 100644 --- a/packages/api/src/client/types/com/atproto/server/createAppPassword.ts +++ b/packages/api/src/client/types/com/atproto/server/createAppPassword.ts @@ -10,6 +10,7 @@ import { CID } from 'multiformats/cid' export interface QueryParams {} export interface InputSchema { + /** A short name for the App Password, to help distinguish them. */ name: string [k: string]: unknown } diff --git a/packages/api/src/client/types/com/atproto/server/describeServer.ts b/packages/api/src/client/types/com/atproto/server/describeServer.ts index fb6c9d5c662..5aca005ec9f 100644 --- a/packages/api/src/client/types/com/atproto/server/describeServer.ts +++ b/packages/api/src/client/types/com/atproto/server/describeServer.ts @@ -12,8 +12,11 @@ export interface QueryParams {} export type InputSchema = undefined export interface OutputSchema { + /** If true, an invite code must be supplied to create an account on this instance. */ inviteCodeRequired?: boolean + /** If true, a phone verification token must be supplied to create an account on this instance. */ phoneVerificationRequired?: boolean + /** List of domain suffixes that can be used in account handles. */ availableUserDomains: string[] links?: Links [k: string]: unknown diff --git a/packages/api/src/client/types/com/atproto/server/getAccountInviteCodes.ts b/packages/api/src/client/types/com/atproto/server/getAccountInviteCodes.ts index d019ed3fa23..5438cbc96d6 100644 --- a/packages/api/src/client/types/com/atproto/server/getAccountInviteCodes.ts +++ b/packages/api/src/client/types/com/atproto/server/getAccountInviteCodes.ts @@ -10,6 +10,7 @@ import * as ComAtprotoServerDefs from './defs' export interface QueryParams { includeUsed?: boolean + /** Controls whether any new 'earned' but not 'created' invites should be created. */ createAvailable?: boolean } diff --git a/packages/api/src/client/types/com/atproto/server/reserveSigningKey.ts b/packages/api/src/client/types/com/atproto/server/reserveSigningKey.ts index f5e515ff5cf..324dee9665a 100644 --- a/packages/api/src/client/types/com/atproto/server/reserveSigningKey.ts +++ b/packages/api/src/client/types/com/atproto/server/reserveSigningKey.ts @@ -10,13 +10,13 @@ import { CID } from 'multiformats/cid' export interface QueryParams {} export interface InputSchema { - /** The did to reserve a new did:key for */ + /** The DID to reserve a key for. */ did?: string [k: string]: unknown } export interface OutputSchema { - /** Public signing key in the form of a did:key. */ + /** The public key for the reserved signing key, in did:key serialization. */ signingKey: string [k: string]: unknown } diff --git a/packages/api/src/client/types/com/atproto/sync/getBlob.ts b/packages/api/src/client/types/com/atproto/sync/getBlob.ts index 57bc271ce5a..83d8b79ca08 100644 --- a/packages/api/src/client/types/com/atproto/sync/getBlob.ts +++ b/packages/api/src/client/types/com/atproto/sync/getBlob.ts @@ -8,7 +8,7 @@ import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' export interface QueryParams { - /** The DID of the repo. */ + /** The DID of the account. */ did: string /** The CID of the blob to fetch */ cid: string diff --git a/packages/api/src/client/types/com/atproto/sync/getRecord.ts b/packages/api/src/client/types/com/atproto/sync/getRecord.ts index e7bbcf36343..1fc9a94b406 100644 --- a/packages/api/src/client/types/com/atproto/sync/getRecord.ts +++ b/packages/api/src/client/types/com/atproto/sync/getRecord.ts @@ -11,6 +11,7 @@ export interface QueryParams { /** The DID of the repo. */ did: string collection: string + /** Record Key */ rkey: string /** An optional past commit CID. */ commit?: string diff --git a/packages/api/src/client/types/com/atproto/sync/getRepo.ts b/packages/api/src/client/types/com/atproto/sync/getRepo.ts index 0a45536779e..53e0883d74e 100644 --- a/packages/api/src/client/types/com/atproto/sync/getRepo.ts +++ b/packages/api/src/client/types/com/atproto/sync/getRepo.ts @@ -10,7 +10,7 @@ import { CID } from 'multiformats/cid' export interface QueryParams { /** The DID of the repo. */ did: string - /** The revision of the repo to catch up from. */ + /** The revision ('rev') of the repo to create a diff from. */ since?: string } diff --git a/packages/api/src/client/types/com/atproto/sync/listRepos.ts b/packages/api/src/client/types/com/atproto/sync/listRepos.ts index 669dba37e85..eccf796acb6 100644 --- a/packages/api/src/client/types/com/atproto/sync/listRepos.ts +++ b/packages/api/src/client/types/com/atproto/sync/listRepos.ts @@ -38,6 +38,7 @@ export function toKnownErr(e: any) { export interface Repo { did: string + /** Current repo commit CID */ head: string rev: string [k: string]: unknown diff --git a/packages/api/src/client/types/com/atproto/sync/notifyOfUpdate.ts b/packages/api/src/client/types/com/atproto/sync/notifyOfUpdate.ts index 2b098982684..f53e4a55385 100644 --- a/packages/api/src/client/types/com/atproto/sync/notifyOfUpdate.ts +++ b/packages/api/src/client/types/com/atproto/sync/notifyOfUpdate.ts @@ -10,7 +10,7 @@ import { CID } from 'multiformats/cid' export interface QueryParams {} export interface InputSchema { - /** Hostname of the service that is notifying of update. */ + /** Hostname of the current service (usually a PDS) that is notifying of update. */ hostname: string [k: string]: unknown } diff --git a/packages/api/src/client/types/com/atproto/sync/requestCrawl.ts b/packages/api/src/client/types/com/atproto/sync/requestCrawl.ts index c07330a6fb1..089eb84e089 100644 --- a/packages/api/src/client/types/com/atproto/sync/requestCrawl.ts +++ b/packages/api/src/client/types/com/atproto/sync/requestCrawl.ts @@ -10,7 +10,7 @@ import { CID } from 'multiformats/cid' export interface QueryParams {} export interface InputSchema { - /** Hostname of the service that is requesting to be crawled. */ + /** Hostname of the current service (eg, PDS) that is requesting to be crawled. */ hostname: string [k: string]: unknown } diff --git a/packages/api/src/client/types/com/atproto/sync/subscribeRepos.ts b/packages/api/src/client/types/com/atproto/sync/subscribeRepos.ts index a4fec035874..708fc80c85b 100644 --- a/packages/api/src/client/types/com/atproto/sync/subscribeRepos.ts +++ b/packages/api/src/client/types/com/atproto/sync/subscribeRepos.ts @@ -7,21 +7,29 @@ import { isObj, hasProp } from '../../../../util' import { lexicons } from '../../../../lexicons' import { CID } from 'multiformats/cid' +/** Represents an update of repository state. Note that empty commits are allowed, which include no repo data changes, but an update to rev and signature. */ export interface Commit { + /** The stream sequence number of this message. */ seq: number + /** DEPRECATED -- unused */ rebase: boolean + /** Indicates that this commit contained too many ops, or data size was too large. Consumers will need to make a separate request to get missing data. */ tooBig: boolean + /** The repo this event comes from. */ repo: string + /** Repo commit object CID. */ commit: CID + /** DEPRECATED -- unused. WARNING -- nullable and optional; stick with optional to ensure golang interoperability. */ prev?: CID | null - /** The rev of the emitted commit. */ + /** The rev of the emitted commit. Note that this information is also in the commit object included in blocks, unless this is a tooBig event. */ rev: string - /** The rev of the last emitted commit from this repo. */ + /** The rev of the last emitted commit from this repo (if any). */ since: string | null - /** CAR file containing relevant blocks. */ + /** CAR file containing relevant blocks, as a diff since the previous repo state. */ blocks: Uint8Array ops: RepoOp[] blobs: CID[] + /** Timestamp of when this message was originally broadcast. */ time: string [k: string]: unknown } @@ -38,6 +46,7 @@ export function validateCommit(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#commit', v) } +/** Represents an update of the account's handle, or transition to/from invalid state. */ export interface Handle { seq: number did: string @@ -58,6 +67,7 @@ export function validateHandle(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#handle', v) } +/** Represents an account moving from one PDS instance to another. NOTE: not implemented; full account migration may introduce a new message instead. */ export interface Migrate { seq: number did: string @@ -78,6 +88,7 @@ export function validateMigrate(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#migrate', v) } +/** Indicates that an account has been deleted. */ export interface Tombstone { seq: number did: string @@ -115,10 +126,11 @@ export function validateInfo(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#info', v) } -/** A repo operation, ie a write of a single record. For creates and updates, CID is the record's CID as of this operation. For deletes, it's null. */ +/** A repo operation, ie a mutation of a single record. */ export interface RepoOp { action: 'create' | 'update' | 'delete' | (string & {}) path: string + /** For creates and updates, the new record CID. For deletions, null. */ cid: CID | null [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index 515098a8f49..8fcf9491077 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -1912,7 +1912,7 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Provides the DID of a repo.', + description: 'Resolves a handle (domain name) to a DID.', parameters: { type: 'params', required: ['handle'], @@ -1946,7 +1946,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Updates the handle of the account.', + description: + "Updates the current account's handle. Verifies handle validity, and updates did:plc document if necessary. Implemented by PDS, and requires auth.", input: { encoding: 'application/json', schema: { @@ -1956,6 +1957,7 @@ export const schemaDict = { handle: { type: 'string', format: 'handle', + description: 'The new handle.', }, }, }, @@ -2046,7 +2048,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Find labels relevant to the provided URI patterns.', + description: + 'Find labels relevant to the provided AT-URI patterns. Public endpoint for moderation services, though may return different or additional results with auth.', parameters: { type: 'params', required: ['uriPatterns'], @@ -2107,13 +2110,14 @@ export const schemaDict = { defs: { main: { type: 'subscription', - description: 'Subscribe to label updates.', + description: + 'Subscribe to stream of labels (and negations). Public endpoint implemented by mod services. Uses same sequencing scheme as repo event stream.', parameters: { type: 'params', properties: { cursor: { type: 'integer', - description: 'The last known event to backfill from.', + description: 'The last known event seq number to backfill from.', }, }, }, @@ -2169,7 +2173,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Report a repo or a record.', + description: + 'Submit a moderation report regarding an atproto account or record. Implemented by moderation services (with PDS proxying), and requires auth.', input: { encoding: 'application/json', schema: { @@ -2178,10 +2183,14 @@ export const schemaDict = { properties: { reasonType: { type: 'ref', + description: + 'Indicates the broad category of violation the report is for.', ref: 'lex:com.atproto.moderation.defs#reasonType', }, reason: { type: 'string', + description: + 'Additional context about the content and violation.', }, subject: { type: 'union', @@ -2292,7 +2301,7 @@ export const schemaDict = { main: { type: 'procedure', description: - 'Apply a batch transaction of creates, updates, and deletes.', + 'Apply a batch transaction of repository creates, updates, and deletes. Requires auth, implemented by PDS.', input: { encoding: 'application/json', schema: { @@ -2302,12 +2311,14 @@ export const schemaDict = { repo: { type: 'string', format: 'at-identifier', - description: 'The handle or DID of the repo.', + description: + 'The handle or DID of the repo (aka, current account).', }, validate: { type: 'boolean', default: true, - description: 'Flag for validating the records.', + description: + "Can be set to 'false' to skip Lexicon schema validation of record data, for all operations.", }, writes: { type: 'array', @@ -2323,6 +2334,8 @@ export const schemaDict = { }, swapCommit: { type: 'string', + description: + 'If provided, the entire operation will fail if the current repo commit CID does not match this value. Used to prevent conflicting repo mutations.', format: 'cid', }, }, @@ -2331,12 +2344,14 @@ export const schemaDict = { errors: [ { name: 'InvalidSwap', + description: + "Indicates that the 'swapCommit' parameter did not match current commit.", }, ], }, create: { type: 'object', - description: 'Create a new record.', + description: 'Operation which creates a new record.', required: ['collection', 'value'], properties: { collection: { @@ -2354,7 +2369,7 @@ export const schemaDict = { }, update: { type: 'object', - description: 'Update an existing record.', + description: 'Operation which updates an existing record.', required: ['collection', 'rkey', 'value'], properties: { collection: { @@ -2371,7 +2386,7 @@ export const schemaDict = { }, delete: { type: 'object', - description: 'Delete an existing record.', + description: 'Operation which deletes an existing record.', required: ['collection', 'rkey'], properties: { collection: { @@ -2391,7 +2406,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Create a new record.', + description: + 'Create a single new repository record. Requires auth, implemented by PDS.', input: { encoding: 'application/json', schema: { @@ -2401,7 +2417,8 @@ export const schemaDict = { repo: { type: 'string', format: 'at-identifier', - description: 'The handle or DID of the repo.', + description: + 'The handle or DID of the repo (aka, current account).', }, collection: { type: 'string', @@ -2410,17 +2427,18 @@ export const schemaDict = { }, rkey: { type: 'string', - description: 'The key of the record.', + description: 'The Record Key.', maxLength: 15, }, validate: { type: 'boolean', default: true, - description: 'Flag for validating the record.', + description: + "Can be set to 'false' to skip Lexicon schema validation of record data.", }, record: { type: 'unknown', - description: 'The record to create.', + description: 'The record itself. Must contain a $type field.', }, swapCommit: { type: 'string', @@ -2451,6 +2469,8 @@ export const schemaDict = { errors: [ { name: 'InvalidSwap', + description: + "Indicates that 'swapCommit' didn't match current repo commit.", }, ], }, @@ -2462,7 +2482,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: "Delete a record, or ensure it doesn't exist.", + description: + "Delete a repository record, or ensure it doesn't exist. Requires auth, implemented by PDS.", input: { encoding: 'application/json', schema: { @@ -2472,7 +2493,8 @@ export const schemaDict = { repo: { type: 'string', format: 'at-identifier', - description: 'The handle or DID of the repo.', + description: + 'The handle or DID of the repo (aka, current account).', }, collection: { type: 'string', @@ -2481,7 +2503,7 @@ export const schemaDict = { }, rkey: { type: 'string', - description: 'The key of the record.', + description: 'The Record Key.', }, swapRecord: { type: 'string', @@ -2513,7 +2535,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Get information about the repo, including the list of collections.', + 'Get information about an account and repository, including the list of collections. Does not require auth.', parameters: { type: 'params', required: ['repo'], @@ -2547,9 +2569,12 @@ export const schemaDict = { }, didDoc: { type: 'unknown', + description: 'The complete DID document for this account.', }, collections: { type: 'array', + description: + 'List of all the collections (NSIDs) for which this repo contains at least one record.', items: { type: 'string', format: 'nsid', @@ -2557,6 +2582,8 @@ export const schemaDict = { }, handleIsCorrect: { type: 'boolean', + description: + 'Indicates if handle is currently valid (resolves bi-directionally)', }, }, }, @@ -2570,7 +2597,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a record.', + description: + 'Get a single record from a repository. Does not require auth.', parameters: { type: 'params', required: ['repo', 'collection', 'rkey'], @@ -2587,7 +2615,7 @@ export const schemaDict = { }, rkey: { type: 'string', - description: 'The key of the record.', + description: 'The Record Key.', }, cid: { type: 'string', @@ -2626,7 +2654,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'List a range of records in a collection.', + description: + 'List a range of records in a repository, matching a specific collection. Does not require auth.', parameters: { type: 'params', required: ['repo', 'collection'], @@ -2712,7 +2741,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Write a record, creating or updating it as needed.', + description: + 'Write a repository record, creating or updating it as needed. Requires auth, implemented by PDS.', input: { encoding: 'application/json', schema: { @@ -2723,7 +2753,8 @@ export const schemaDict = { repo: { type: 'string', format: 'at-identifier', - description: 'The handle or DID of the repo.', + description: + 'The handle or DID of the repo (aka, current account).', }, collection: { type: 'string', @@ -2732,13 +2763,14 @@ export const schemaDict = { }, rkey: { type: 'string', - description: 'The key of the record.', + description: 'The Record Key.', maxLength: 15, }, validate: { type: 'boolean', default: true, - description: 'Flag for validating the record.', + description: + "Can be set to 'false' to skip Lexicon schema validation of record data.", }, record: { type: 'unknown', @@ -2748,7 +2780,7 @@ export const schemaDict = { type: 'string', format: 'cid', description: - 'Compare and swap with the previous record by CID.', + 'Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation', }, swapCommit: { type: 'string', @@ -2812,7 +2844,7 @@ export const schemaDict = { main: { type: 'procedure', description: - 'Upload a new blob to be added to repo in a later request.', + 'Upload a new blob, to be referenced from a repository record. The blob will be deleted if it is not referenced within a time window (eg, minutes). Blob restrictions (mimetype, size, etc) are enforced when the reference is created. Requires auth, implemented by PDS.', input: { encoding: '*/*', }, @@ -2877,7 +2909,7 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Create an account.', + description: 'Create an account. Implemented by PDS.', input: { encoding: 'application/json', schema: { @@ -2890,10 +2922,13 @@ export const schemaDict = { handle: { type: 'string', format: 'handle', + description: 'Requested handle for the account.', }, did: { type: 'string', format: 'did', + description: + 'Pre-existing atproto DID, being imported to a new account.', }, inviteCode: { type: 'string', @@ -2906,12 +2941,18 @@ export const schemaDict = { }, password: { type: 'string', + description: + 'Initial account password. May need to meet instance-specific password strength requirements.', }, recoveryKey: { type: 'string', + description: + 'DID PLC rotation key (aka, recovery key) to be included in PLC creation operation.', }, plcOp: { type: 'unknown', + description: + 'A signed DID PLC operation to be submitted as part of importing an existing account to this instance. NOTE: this optional field may be updated when full account migration is implemented.', }, }, }, @@ -2920,6 +2961,8 @@ export const schemaDict = { encoding: 'application/json', schema: { type: 'object', + description: + 'Account login session returned on successful account creation.', required: ['accessJwt', 'refreshJwt', 'handle', 'did'], properties: { accessJwt: { @@ -2935,9 +2978,11 @@ export const schemaDict = { did: { type: 'string', format: 'did', + description: 'The DID of the new account.', }, didDoc: { type: 'unknown', + description: 'Complete DID document.', }, }, }, @@ -2983,6 +3028,8 @@ export const schemaDict = { properties: { name: { type: 'string', + description: + 'A short name for the App Password, to help distinguish them.', }, }, }, @@ -3250,7 +3297,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: "Delete an actor's account with a token and password.", + description: + "Delete an actor's account with a token and password. Can only be called after requesting a deletion token. Requires auth.", input: { encoding: 'application/json', schema: { @@ -3287,7 +3335,7 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Delete the current session.', + description: 'Delete the current session. Requires auth.', }, }, }, @@ -3298,7 +3346,7 @@ export const schemaDict = { main: { type: 'query', description: - "Get a document describing the service's accounts configuration.", + "Describes the server's account creation requirements and capabilities. Implemented by PDS.", output: { encoding: 'application/json', schema: { @@ -3307,18 +3355,25 @@ export const schemaDict = { properties: { inviteCodeRequired: { type: 'boolean', + description: + 'If true, an invite code must be supplied to create an account on this instance.', }, phoneVerificationRequired: { type: 'boolean', + description: + 'If true, a phone verification token must be supplied to create an account on this instance.', }, availableUserDomains: { type: 'array', + description: + 'List of domain suffixes that can be used in account handles.', items: { type: 'string', }, }, links: { type: 'ref', + description: 'URLs of service policy documents.', ref: 'lex:com.atproto.server.describeServer#links', }, }, @@ -3344,7 +3399,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get all invite codes for a given account.', + description: + 'Get all invite codes for the current account. Requires auth.', parameters: { type: 'params', properties: { @@ -3355,6 +3411,8 @@ export const schemaDict = { createAvailable: { type: 'boolean', default: true, + description: + "Controls whether any new 'earned' but not 'created' invites should be created.", }, }, }, @@ -3388,7 +3446,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get information about the current session.', + description: + 'Get information about the current auth session. Requires auth.', output: { encoding: 'application/json', schema: { @@ -3468,7 +3527,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Refresh an authentication session.', + description: + "Refresh an authentication session. Requires auth using the 'refreshJwt' (not the 'accessJwt').", output: { encoding: 'application/json', schema: { @@ -3574,7 +3634,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Reserve a repo signing key for account creation.', + description: + 'Reserve a repo signing key, for use with account creation. Necessary so that a DID PLC update operation can be constructed during an account migraiton. Public and does not require auth; implemented by PDS. NOTE: this endpoint may change when full account migration is implemented.', input: { encoding: 'application/json', schema: { @@ -3582,7 +3643,7 @@ export const schemaDict = { properties: { did: { type: 'string', - description: 'The did to reserve a new did:key for', + description: 'The DID to reserve a key for.', }, }, }, @@ -3595,7 +3656,8 @@ export const schemaDict = { properties: { signingKey: { type: 'string', - description: 'Public signing key in the form of a did:key.', + description: + 'The public key for the reserved signing key, in did:key serialization.', }, }, }, @@ -3702,7 +3764,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a blob associated with a given repo.', + description: + 'Get a blob associated with a given account. Returns the full blob as originally uploaded. Does not require auth; implemented by PDS.', parameters: { type: 'params', required: ['did', 'cid'], @@ -3710,7 +3773,7 @@ export const schemaDict = { did: { type: 'string', format: 'did', - description: 'The DID of the repo.', + description: 'The DID of the account.', }, cid: { type: 'string', @@ -3731,7 +3794,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get blocks from a given repo.', + description: + 'Get data blocks from a given repo, by CID. For example, intermediate MST nodes, or records. Does not require auth; implemented by PDS.', parameters: { type: 'params', required: ['did', 'cids'], @@ -3826,7 +3890,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get the current commit CID & revision of the repo.', + description: + 'Get the current commit CID & revision of the specified repo. Does not require auth.', parameters: { type: 'params', required: ['did'], @@ -3869,7 +3934,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Get blocks needed for existence or non-existence of record.', + 'Get data blocks needed to prove the existence or non-existence of record in the current version of repo. Does not require auth.', parameters: { type: 'params', required: ['did', 'collection', 'rkey'], @@ -3885,6 +3950,7 @@ export const schemaDict = { }, rkey: { type: 'string', + description: 'Record Key', }, commit: { type: 'string', @@ -3906,7 +3972,7 @@ export const schemaDict = { main: { type: 'query', description: - "Gets the DID's repo, optionally catching up from a specific revision.", + "Download a repository export as CAR file. Optionally only a 'diff' since a previous revision. Does not require auth; implemented by PDS.", parameters: { type: 'params', required: ['did'], @@ -3918,7 +3984,8 @@ export const schemaDict = { }, since: { type: 'string', - description: 'The revision of the repo to catch up from.', + description: + "The revision ('rev') of the repo to create a diff from.", }, }, }, @@ -3934,7 +4001,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'List blob CIDs since some revision.', + description: + 'List blob CIDso for an account, since some repo revision. Does not require auth; implemented by PDS.', parameters: { type: 'params', required: ['did'], @@ -3987,7 +4055,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'List DIDs and root CIDs of hosted repos.', + description: + 'Enumerates all the DID, rev, and commit CID for all repos hosted by this service. Does not require auth; implemented by PDS and Relay.', parameters: { type: 'params', properties: { @@ -4033,6 +4102,7 @@ export const schemaDict = { head: { type: 'string', format: 'cid', + description: 'Current repo commit CID', }, rev: { type: 'string', @@ -4048,7 +4118,7 @@ export const schemaDict = { main: { type: 'procedure', description: - 'Notify a crawling service of a recent update; often when a long break between updates causes the connection with the crawling service to break.', + 'Notify a crawling service of a recent update, and that crawling should resume. Intended use is after a gap between repo stream events caused the crawling service to disconnect. Does not require auth; implemented by Relay.', input: { encoding: 'application/json', schema: { @@ -4058,7 +4128,7 @@ export const schemaDict = { hostname: { type: 'string', description: - 'Hostname of the service that is notifying of update.', + 'Hostname of the current service (usually a PDS) that is notifying of update.', }, }, }, @@ -4072,7 +4142,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Request a service to persistently crawl hosted repos.', + description: + 'Request a service to persistently crawl hosted repos. Expected use is new PDS instances declaring their existence to Relays. Does not require auth.', input: { encoding: 'application/json', schema: { @@ -4082,7 +4153,7 @@ export const schemaDict = { hostname: { type: 'string', description: - 'Hostname of the service that is requesting to be crawled.', + 'Hostname of the current service (eg, PDS) that is requesting to be crawled.', }, }, }, @@ -4096,13 +4167,14 @@ export const schemaDict = { defs: { main: { type: 'subscription', - description: 'Subscribe to repo updates.', + description: + 'Repository event stream, aka Firehose endpoint. Outputs repo commits with diff data, and identity update events, for all repositories on the current server. See the atproto specifications for details around stream sequencing, repo versioning, CAR diff format, and more. Public and does not require auth; implemented by PDS and Relay.', parameters: { type: 'params', properties: { cursor: { type: 'integer', - description: 'The last known event to backfill from.', + description: 'The last known event seq number to backfill from.', }, }, }, @@ -4124,11 +4196,15 @@ export const schemaDict = { }, { name: 'ConsumerTooSlow', + description: + 'If the consumer of the stream can not keep up with events, and a backlog gets too large, the server will drop the connection.', }, ], }, commit: { type: 'object', + description: + 'Represents an update of repository state. Note that empty commits are allowed, which include no repo data changes, but an update to rev and signature.', required: [ 'seq', 'rebase', @@ -4146,34 +4222,45 @@ export const schemaDict = { properties: { seq: { type: 'integer', + description: 'The stream sequence number of this message.', }, rebase: { type: 'boolean', + description: 'DEPRECATED -- unused', }, tooBig: { type: 'boolean', + description: + 'Indicates that this commit contained too many ops, or data size was too large. Consumers will need to make a separate request to get missing data.', }, repo: { type: 'string', format: 'did', + description: 'The repo this event comes from.', }, commit: { type: 'cid-link', + description: 'Repo commit object CID.', }, prev: { type: 'cid-link', + description: + 'DEPRECATED -- unused. WARNING -- nullable and optional; stick with optional to ensure golang interoperability.', }, rev: { type: 'string', - description: 'The rev of the emitted commit.', + description: + 'The rev of the emitted commit. Note that this information is also in the commit object included in blocks, unless this is a tooBig event.', }, since: { type: 'string', - description: 'The rev of the last emitted commit from this repo.', + description: + 'The rev of the last emitted commit from this repo (if any).', }, blocks: { type: 'bytes', - description: 'CAR file containing relevant blocks.', + description: + 'CAR file containing relevant blocks, as a diff since the previous repo state.', maxLength: 1000000, }, ops: { @@ -4181,6 +4268,8 @@ export const schemaDict = { items: { type: 'ref', ref: 'lex:com.atproto.sync.subscribeRepos#repoOp', + description: + 'List of repo mutation operations in this commit (eg, records created, updated, or deleted).', }, maxLength: 200, }, @@ -4188,16 +4277,22 @@ export const schemaDict = { type: 'array', items: { type: 'cid-link', + description: + 'List of new blobs (by CID) referenced by records in this commit.', }, }, time: { type: 'string', format: 'datetime', + description: + 'Timestamp of when this message was originally broadcast.', }, }, }, handle: { type: 'object', + description: + "Represents an update of the account's handle, or transition to/from invalid state.", required: ['seq', 'did', 'handle', 'time'], properties: { seq: { @@ -4219,6 +4314,8 @@ export const schemaDict = { }, migrate: { type: 'object', + description: + 'Represents an account moving from one PDS instance to another. NOTE: not implemented; full account migration may introduce a new message instead.', required: ['seq', 'did', 'migrateTo', 'time'], nullable: ['migrateTo'], properties: { @@ -4240,6 +4337,7 @@ export const schemaDict = { }, tombstone: { type: 'object', + description: 'Indicates that an account has been deleted.', required: ['seq', 'did', 'time'], properties: { seq: { @@ -4270,8 +4368,7 @@ export const schemaDict = { }, repoOp: { type: 'object', - description: - "A repo operation, ie a write of a single record. For creates and updates, CID is the record's CID as of this operation. For deletes, it's null.", + description: 'A repo operation, ie a mutation of a single record.', required: ['action', 'path', 'cid'], nullable: ['cid'], properties: { @@ -4284,6 +4381,8 @@ export const schemaDict = { }, cid: { type: 'cid-link', + description: + 'For creates and updates, the new record CID. For deletions, null.', }, }, }, @@ -4324,7 +4423,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Fetch all labels from a labeler created after a certain date.', + 'Fetch all labels from a labeler created after a certain date. DEPRECATED: use queryLabels or subscribeLabels instead', parameters: { type: 'params', properties: { @@ -4440,7 +4539,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Transfer an account.', + description: + 'Transfer an account. NOTE: temporary method, necessarily how account migration will be implemented.', input: { encoding: 'application/json', schema: { @@ -4513,7 +4613,6 @@ export const schemaDict = { AppBskyActorDefs: { lexicon: 1, id: 'app.bsky.actor.defs', - description: 'A reference to an actor in the network.', defs: { profileViewBasic: { type: 'object', @@ -4646,6 +4745,8 @@ export const schemaDict = { }, viewerState: { type: 'object', + description: + "Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests.", properties: { muted: { type: 'boolean', @@ -4815,7 +4916,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get private preferences attached to the account.', + description: + 'Get private preferences attached to the current account. Expected use is synchronization between multiple devices, and import/export during account migration. Requires auth.', parameters: { type: 'params', properties: {}, @@ -4842,7 +4944,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get detailed profile view of an actor.', + description: + 'Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth.', parameters: { type: 'params', required: ['actor'], @@ -4850,6 +4953,7 @@ export const schemaDict = { actor: { type: 'string', format: 'at-identifier', + description: 'Handle or DID of account to fetch profile of.', }, }, }, @@ -4909,7 +5013,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of suggested actors, used for discovery.', + description: + 'Get a list of suggested actors. Expected use is discovery of accounts to follow during new account onboarding.', parameters: { type: 'params', properties: { @@ -4952,7 +5057,7 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a profile.', + description: 'A declaration of a Bluesky account profile.', key: 'literal:self', record: { type: 'object', @@ -4964,21 +5069,28 @@ export const schemaDict = { }, description: { type: 'string', + description: 'Free-form profile description text.', maxGraphemes: 256, maxLength: 2560, }, avatar: { type: 'blob', + description: + "Small image to be displayed next to posts from account. AKA, 'profile picture'", accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, banner: { type: 'blob', + description: + 'Larger horizontal image to display behind profile view.', accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, labels: { type: 'union', + description: + 'Self-label values, specific to the Bluesky application, on the overall account.', refs: ['lex:com.atproto.label.defs#selfLabels'], }, }, @@ -5015,7 +5127,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Find actors (profiles) matching search criteria.', + description: + 'Find actors (profiles) matching search criteria. Does not require auth.', parameters: { type: 'params', properties: { @@ -5067,7 +5180,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Find actor suggestions for a prefix search term.', + description: + 'Find actor suggestions for a prefix search term. Expected use is for auto-completion during text field entry. Does not require auth.', parameters: { type: 'params', properties: { @@ -5109,11 +5223,11 @@ export const schemaDict = { AppBskyEmbedExternal: { lexicon: 1, id: 'app.bsky.embed.external', - description: - 'A representation of some externally linked content, embedded in another form of content.', defs: { main: { type: 'object', + description: + "A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post).", required: ['external'], properties: { external: { @@ -5177,7 +5291,7 @@ export const schemaDict = { AppBskyEmbedImages: { lexicon: 1, id: 'app.bsky.embed.images', - description: 'A set of images embedded in some other form of content.', + description: 'A set of images embedded in a Bluesky record (eg, a post).', defs: { main: { type: 'object', @@ -5204,6 +5318,8 @@ export const schemaDict = { }, alt: { type: 'string', + description: + 'Alt text description of the image, for accessibility.', }, aspectRatio: { type: 'ref', @@ -5247,12 +5363,18 @@ export const schemaDict = { properties: { thumb: { type: 'string', + description: + 'Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View.', }, fullsize: { type: 'string', + description: + 'Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View.', }, alt: { type: 'string', + description: + 'Alt text description of the image, for accessibility.', }, aspectRatio: { type: 'ref', @@ -5266,7 +5388,7 @@ export const schemaDict = { lexicon: 1, id: 'app.bsky.embed.record', description: - 'A representation of a record embedded in another form of content.', + 'A representation of a record embedded in a Bluesky record (eg, a post). For example, a quote-post, or sharing a feed generator record.', defs: { main: { type: 'object', @@ -5312,6 +5434,7 @@ export const schemaDict = { }, value: { type: 'unknown', + description: 'The record data itself.', }, labels: { type: 'array', @@ -5376,7 +5499,7 @@ export const schemaDict = { lexicon: 1, id: 'app.bsky.embed.recordWithMedia', description: - 'A representation of a record embedded in another form of content, alongside other compatible embeds.', + 'A representation of a record embedded in a Bluesky record (eg, a post), alongside other compatible embeds. For example, a quote post and image, or a quote post and external URL card.', defs: { main: { type: 'object', @@ -5475,6 +5598,8 @@ export const schemaDict = { }, viewerState: { type: 'object', + description: + "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests.", properties: { repost: { type: 'string', @@ -5735,7 +5860,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Get information about a feed generator, including policies and offered feed URIs.', + 'Get information about a feed generator, including policies and offered feed URIs. Does not require auth; implemented by Feed Generator services (not App View).', output: { encoding: 'application/json', schema: { @@ -5790,7 +5915,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of the existence of a feed generator.', + description: + 'Record declaring of the existence of a feed generator, and containing metadata about it. The record can exist in any repository.', key: 'any', record: { type: 'object', @@ -5824,6 +5950,7 @@ export const schemaDict = { }, labels: { type: 'union', + description: 'Self-label values', refs: ['lex:com.atproto.label.defs#selfLabels'], }, createdAt: { @@ -5841,7 +5968,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of feeds created by the actor.', + description: + "Get a list of feeds (feed generator records) created by the actor (in the actor's repo).", parameters: { type: 'params', required: ['actor'], @@ -5889,7 +6017,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of posts liked by an actor.', + description: + 'Get a list of posts liked by an actor. Does not require auth.', parameters: { type: 'params', required: ['actor'], @@ -5945,7 +6074,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: "Get a view of an actor's feed.", + description: + "Get a view of an actor's 'author feed' (post and reposts by the author). Does not require auth.", parameters: { type: 'params', required: ['actor'], @@ -5965,6 +6095,8 @@ export const schemaDict = { }, filter: { type: 'string', + description: + 'Combinations of post/repost types to include in response.', knownValues: [ 'posts_with_replies', 'posts_no_replies', @@ -6012,7 +6144,7 @@ export const schemaDict = { main: { type: 'query', description: - "Get a hydrated feed from an actor's selected feed generator.", + "Get a hydrated feed from an actor's selected feed generator. Implemented by App View.", parameters: { type: 'params', required: ['feed'], @@ -6065,7 +6197,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get information about a feed generator.', + description: + 'Get information about a feed generator. Implemented by AppView.', parameters: { type: 'params', required: ['feed'], @@ -6073,6 +6206,7 @@ export const schemaDict = { feed: { type: 'string', format: 'at-uri', + description: 'AT-URI of the feed generator record.', }, }, }, @@ -6088,9 +6222,13 @@ export const schemaDict = { }, isOnline: { type: 'boolean', + description: + 'Indicates whether the feed generator service has been online recently, or else seems to be inactive.', }, isValid: { type: 'boolean', + description: + 'Indicates whether the feed generator service is compatible with the record declaration.', }, }, }, @@ -6143,7 +6281,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a skeleton of a feed provided by a feed generator.', + description: + 'Get a skeleton of a feed provided by a feed generator. Auth is optional, depending on provider requirements, and provides the DID of the requester. Implemented by Feed Generator Service.', parameters: { type: 'params', required: ['feed'], @@ -6151,6 +6290,8 @@ export const schemaDict = { feed: { type: 'string', format: 'at-uri', + description: + 'Reference to feed generator record describing the specific feed being requested.', }, limit: { type: 'integer', @@ -6196,7 +6337,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get the list of likes.', + description: + 'Get like records which reference a subject (by AT-URI and CID).', parameters: { type: 'params', required: ['uri'], @@ -6204,10 +6346,13 @@ export const schemaDict = { uri: { type: 'string', format: 'at-uri', + description: 'AT-URI of the subject (eg, a post record).', }, cid: { type: 'string', format: 'cid', + description: + 'CID of the subject record (aka, specific version of record), to filter likes.', }, limit: { type: 'integer', @@ -6274,7 +6419,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a view of a recent posts from actors in a list.', + description: + 'Get a feed of recent posts from a list (posts and reposts from any actors on the list). Does not require auth.', parameters: { type: 'params', required: ['list'], @@ -6282,6 +6428,7 @@ export const schemaDict = { list: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) to the list record.', }, limit: { type: 'integer', @@ -6327,7 +6474,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get posts in a thread.', + description: + 'Get posts in a thread. Does not require auth, but additional metadata and filtering will be applied for authed requests.', parameters: { type: 'params', required: ['uri'], @@ -6335,15 +6483,20 @@ export const schemaDict = { uri: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) to post record.', }, depth: { type: 'integer', + description: + 'How many levels of reply depth should be included in response.', default: 6, minimum: 0, maximum: 1000, }, parentHeight: { type: 'integer', + description: + 'How many levels of parent (and grandparent, etc) post to include.', default: 80, minimum: 0, maximum: 1000, @@ -6381,13 +6534,15 @@ export const schemaDict = { defs: { main: { type: 'query', - description: "Get a view of an actor's feed.", + description: + "Gets post views for a specified list of posts (by AT-URI). This is sometimes referred to as 'hydrating' a 'feed skeleton'.", parameters: { type: 'params', required: ['uris'], properties: { uris: { type: 'array', + description: 'List of post AT-URIs to return hydrated views for.', items: { type: 'string', format: 'at-uri', @@ -6421,7 +6576,7 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of reposts.', + description: 'Get a list of reposts for a given post.', parameters: { type: 'params', required: ['uri'], @@ -6429,10 +6584,13 @@ export const schemaDict = { uri: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) of post record', }, cid: { type: 'string', format: 'cid', + description: + 'If supplied, filters to reposts of specific version (by CID) of the post record.', }, limit: { type: 'integer', @@ -6481,7 +6639,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of suggested feeds for the viewer.', + description: + 'Get a list of suggested feeds (feed generators) for the requesting account.', parameters: { type: 'params', properties: { @@ -6524,12 +6683,15 @@ export const schemaDict = { defs: { main: { type: 'query', - description: "Get a view of the actor's home timeline.", + description: + "Get a view of the requesting account's home timeline. This is expected to be some form of reverse-chronological feed.", parameters: { type: 'params', properties: { algorithm: { type: 'string', + description: + "Variant 'algorithm' for timeline. Implementation-specific. NOTE: most feed flexibility has been moved to feed generator mechanism.", }, limit: { type: 'integer', @@ -6570,7 +6732,7 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a like.', + description: "Record declaring a 'like' of a piece of subject content.", key: 'tid', record: { type: 'object', @@ -6595,7 +6757,7 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a post.', + description: 'Record containing a Bluesky post.', key: 'tid', record: { type: 'object', @@ -6605,10 +6767,12 @@ export const schemaDict = { type: 'string', maxLength: 3000, maxGraphemes: 300, + description: + 'The primary post content. May be an empty string, if there are embeds.', }, entities: { type: 'array', - description: 'Deprecated: replaced by app.bsky.richtext.facet.', + description: 'DEPRECATED: replaced by app.bsky.richtext.facet.', items: { type: 'ref', ref: 'lex:app.bsky.feed.post#entity', @@ -6616,6 +6780,8 @@ export const schemaDict = { }, facets: { type: 'array', + description: + 'Annotations of text (mentions, URLs, hashtags, etc)', items: { type: 'ref', ref: 'lex:app.bsky.richtext.facet', @@ -6636,6 +6802,8 @@ export const schemaDict = { }, langs: { type: 'array', + description: + 'Indicates human language of post primary text content.', maxLength: 3, items: { type: 'string', @@ -6644,21 +6812,25 @@ export const schemaDict = { }, labels: { type: 'union', + description: + 'Self-label values for this post. Effectively content warnings.', refs: ['lex:com.atproto.label.defs#selfLabels'], }, tags: { type: 'array', + description: 'Additional non-inline tags describing this post.', maxLength: 8, items: { type: 'string', maxLength: 640, maxGraphemes: 64, }, - description: 'Additional non-inline tags describing this post.', }, createdAt: { type: 'string', format: 'datetime', + description: + 'Client-declared timestamp when this post was originally created.', }, }, }, @@ -6718,7 +6890,8 @@ export const schemaDict = { id: 'app.bsky.feed.repost', defs: { main: { - description: 'A declaration of a repost.', + description: + "Record representing a 'repost' of an existing Bluesky post.", type: 'record', key: 'tid', record: { @@ -6744,7 +6917,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Find posts matching search criteria.', + description: + 'Find posts matching search criteria, returning views of those posts.', parameters: { type: 'params', required: ['q'], @@ -6807,7 +6981,7 @@ export const schemaDict = { type: 'record', key: 'tid', description: - "Defines interaction gating rules for a thread. The rkey of the threadgate record should match the rkey of the thread's root post.", + "Record defining interaction gating rules for a thread (aka, reply controls). The record key (rkey) of the threadgate record must match the record key of the thread's root post, and that record must be in the same repository..", record: { type: 'object', required: ['post', 'createdAt'], @@ -6815,6 +6989,7 @@ export const schemaDict = { post: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) to the post record.', }, allow: { type: 'array', @@ -6864,7 +7039,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a block.', + description: + "Record declaring a 'block' relationship against another account. NOTE: blocks are public in Bluesky; see blog posts for details.", key: 'tid', record: { type: 'object', @@ -6873,6 +7049,7 @@ export const schemaDict = { subject: { type: 'string', format: 'did', + description: 'DID of the account to be blocked.', }, createdAt: { type: 'string', @@ -7061,7 +7238,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a social follow.', + description: + "Record declaring a social 'follow' relationship of another account. Duplicate follows will be ignored by the AppView.", key: 'tid', record: { type: 'object', @@ -7086,7 +7264,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of who the actor is blocking.', + description: + 'Enumerates which accounts the requesting account is currently blocking. Requires auth.', parameters: { type: 'params', properties: { @@ -7129,7 +7308,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: "Get a list of an actor's followers.", + description: + 'Enumerates accounts which follow a specified account (actor).', parameters: { type: 'params', required: ['actor'], @@ -7181,7 +7361,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of who the actor follows.', + description: + 'Enumerates accounts which a specified account (actor) follows.', parameters: { type: 'params', required: ['actor'], @@ -7233,7 +7414,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of actors.', + description: + "Gets a 'view' (with additional context) of a specified list.", parameters: { type: 'params', required: ['list'], @@ -7241,6 +7423,7 @@ export const schemaDict = { list: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) of the list record to hydrate.', }, limit: { type: 'integer', @@ -7285,7 +7468,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get lists that the actor is blocking.', + description: + 'Get mod lists that the requesting account (actor) is blocking. Requires auth.', parameters: { type: 'params', properties: { @@ -7328,7 +7512,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get lists that the actor is muting.', + description: + 'Enumerates mod lists that the requesting account (actor) currently has muted. Requires auth.', parameters: { type: 'params', properties: { @@ -7371,7 +7556,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of lists that belong to an actor.', + description: + 'Enumerates the lists created by a specified account (actor).', parameters: { type: 'params', required: ['actor'], @@ -7379,6 +7565,7 @@ export const schemaDict = { actor: { type: 'string', format: 'at-identifier', + description: 'The account (actor) to enumerate lists from.', }, limit: { type: 'integer', @@ -7419,7 +7606,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of who the actor mutes.', + description: + 'Enumerates accounts that the requesting account (actor) currently has muted. Requires auth.', parameters: { type: 'params', properties: { @@ -7463,7 +7651,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Enumerates public relationships between one account, and a list of other accounts', + 'Enumerates public relationships between one account, and a list of other accounts. Does not require auth.', parameters: { type: 'params', required: ['actor'], @@ -7471,9 +7659,12 @@ export const schemaDict = { actor: { type: 'string', format: 'at-identifier', + description: 'Primary account requesting relationships for.', }, others: { type: 'array', + description: + "List of 'other' accounts to be related back to the primary.", maxLength: 30, items: { type: 'string', @@ -7521,7 +7712,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get suggested follows related to a given actor.', + description: + 'Enumerates follows similar to a given account (actor). Expected use is to recommend additional accounts immediately after following one account.', parameters: { type: 'params', required: ['actor'], @@ -7557,7 +7749,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a list of actors.', + description: + 'Record representing a list of accounts (actors). Scope includes both moderation-oriented lists and curration-oriented lists.', key: 'tid', record: { type: 'object', @@ -7565,12 +7758,15 @@ export const schemaDict = { properties: { purpose: { type: 'ref', + description: + 'Defines the purpose of the list (aka, moderation-oriented or curration-oriented)', ref: 'lex:app.bsky.graph.defs#listPurpose', }, name: { type: 'string', maxLength: 64, minLength: 1, + description: 'Display name for list; can not be empty.', }, description: { type: 'string', @@ -7608,7 +7804,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A block of an entire list of actors.', + description: + 'Record representing a block relationship against an entire an entire list of accounts (actors).', key: 'tid', record: { type: 'object', @@ -7617,6 +7814,7 @@ export const schemaDict = { subject: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) to the mod list record.', }, createdAt: { type: 'string', @@ -7633,7 +7831,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'An item under a declared list of actors.', + description: + "Record representing an account's inclusion on a specific list. The AppView will ignore duplicate listitem records.", key: 'tid', record: { type: 'object', @@ -7642,10 +7841,13 @@ export const schemaDict = { subject: { type: 'string', format: 'did', + description: 'The account which is included on the list.', }, list: { type: 'string', format: 'at-uri', + description: + 'Reference (AT-URI) to the list record (app.bsky.graph.list).', }, createdAt: { type: 'string', @@ -7662,7 +7864,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Mute an actor by DID or handle.', + description: + 'Creates a mute relationship for the specified account. Mutes are private in Bluesky. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7685,7 +7888,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Mute a list of actors.', + description: + 'Creates a mute relationship for the specified list of accounts. Mutes are private in Bluesky. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7708,7 +7912,7 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Unmute an actor by DID or handle.', + description: 'Unmutes the specified account. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7731,7 +7935,7 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Unmute a list of actors.', + description: 'Unmutes the specified list of accounts. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7754,7 +7958,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get the count of unread notifications.', + description: + 'Count the number of unread notifications for the requesting account. Requires auth.', parameters: { type: 'params', properties: { @@ -7785,7 +7990,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of notifications.', + description: + 'Enumerate notifications for the requesting account. Requires auth.', parameters: { type: 'params', properties: { @@ -7896,7 +8102,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Register for push notifications with a service.', + description: + 'Register to receive push notifications, via a specified service, for the requesting account. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7929,7 +8136,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Notify server that the user has seen notifications.', + description: + 'Notify server that the requesting account has seen notifications. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7952,6 +8160,7 @@ export const schemaDict = { defs: { main: { type: 'object', + description: 'Annotation of a sub-string within rich text.', required: ['index', 'features'], properties: { index: { @@ -7973,7 +8182,8 @@ export const schemaDict = { }, mention: { type: 'object', - description: 'A facet feature for actor mentions.', + description: + "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID.", required: ['did'], properties: { did: { @@ -7984,7 +8194,8 @@ export const schemaDict = { }, link: { type: 'object', - description: 'A facet feature for links.', + description: + 'Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL.', required: ['uri'], properties: { uri: { @@ -7995,7 +8206,8 @@ export const schemaDict = { }, tag: { type: 'object', - description: 'A hashtag.', + description: + "Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags').", required: ['tag'], properties: { tag: { @@ -8008,7 +8220,7 @@ export const schemaDict = { byteSlice: { type: 'object', description: - 'A text segment. Start is inclusive, end is exclusive. Indices are for utf8-encoded strings.', + 'Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets.', required: ['byteStart', 'byteEnd'], properties: { byteStart: { diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts index 8cdcafcb72f..ff49c40dc0b 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts @@ -82,6 +82,7 @@ export function validateProfileViewDetailed(v: unknown): ValidationResult { return lexicons.validate('app.bsky.actor.defs#profileViewDetailed', v) } +/** Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests. */ export interface ViewerState { muted?: boolean mutedByList?: AppBskyGraphDefs.ListViewBasic diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/getProfile.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/getProfile.ts index be58c73a233..5a7b1f25bfc 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/getProfile.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/getProfile.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams { + /** Handle or DID of account to fetch profile of. */ actor: string } diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/profile.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/profile.ts index 7dbc4c1ccec..8810ce7bed9 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/profile.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/profile.ts @@ -9,8 +9,11 @@ import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface Record { displayName?: string + /** Free-form profile description text. */ description?: string + /** Small image to be displayed next to posts from account. AKA, 'profile picture' */ avatar?: BlobRef + /** Larger horizontal image to display behind profile view. */ banner?: BlobRef labels?: | ComAtprotoLabelDefs.SelfLabels diff --git a/packages/bsky/src/lexicon/types/app/bsky/embed/external.ts b/packages/bsky/src/lexicon/types/app/bsky/embed/external.ts index f42a6cfd95c..b137ee4b6f5 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/embed/external.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/embed/external.ts @@ -6,6 +6,7 @@ import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' +/** A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post). */ export interface Main { external: External [k: string]: unknown diff --git a/packages/bsky/src/lexicon/types/app/bsky/embed/images.ts b/packages/bsky/src/lexicon/types/app/bsky/embed/images.ts index 4864fad3dea..96399867a1a 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/embed/images.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/embed/images.ts @@ -26,6 +26,7 @@ export function validateMain(v: unknown): ValidationResult { export interface Image { image: BlobRef + /** Alt text description of the image, for accessibility. */ alt: string aspectRatio?: AspectRatio [k: string]: unknown @@ -76,8 +77,11 @@ export function validateView(v: unknown): ValidationResult { } export interface ViewImage { + /** Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View. */ thumb: string + /** Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View. */ fullsize: string + /** Alt text description of the image, for accessibility. */ alt: string aspectRatio?: AspectRatio [k: string]: unknown diff --git a/packages/bsky/src/lexicon/types/app/bsky/embed/record.ts b/packages/bsky/src/lexicon/types/app/bsky/embed/record.ts index cea5742a45e..dbe7f13152b 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/embed/record.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/embed/record.ts @@ -57,6 +57,7 @@ export interface ViewRecord { uri: string cid: string author: AppBskyActorDefs.ProfileViewBasic + /** The record data itself. */ value: {} labels?: ComAtprotoLabelDefs.Label[] embeds?: ( diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/defs.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/defs.ts index 382d3f58ecf..261d8a622ec 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/defs.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/defs.ts @@ -45,6 +45,7 @@ export function validatePostView(v: unknown): ValidationResult { return lexicons.validate('app.bsky.feed.defs#postView', v) } +/** Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests. */ export interface ViewerState { repost?: string like?: string diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts index 8f8e038c71f..017c7a6a2d4 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts @@ -13,6 +13,7 @@ export interface QueryParams { actor: string limit: number cursor?: string + /** Combinations of post/repost types to include in response. */ filter: | 'posts_with_replies' | 'posts_no_replies' diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts index b65a5151d46..7ab89057a8c 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** AT-URI of the feed generator record. */ feed: string } @@ -17,7 +18,9 @@ export type InputSchema = undefined export interface OutputSchema { view: AppBskyFeedDefs.GeneratorView + /** Indicates whether the feed generator service has been online recently, or else seems to be inactive. */ isOnline: boolean + /** Indicates whether the feed generator service is compatible with the record declaration. */ isValid: boolean [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts index ab1911ecb87..ca1cef20f08 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** Reference to feed generator record describing the specific feed being requested. */ feed: string limit: number cursor?: string diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getLikes.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getLikes.ts index c7e8c860bd6..275d99bba3d 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getLikes.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getLikes.ts @@ -10,7 +10,9 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { + /** AT-URI of the subject (eg, a post record). */ uri: string + /** CID of the subject record (aka, specific version of record), to filter likes. */ cid?: string limit: number cursor?: string diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getListFeed.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getListFeed.ts index ab157788f72..84e12deaa92 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getListFeed.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getListFeed.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** Reference (AT-URI) to the list record. */ list: string limit: number cursor?: string diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getPostThread.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getPostThread.ts index 9e81da0eff6..ae232fd91a2 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getPostThread.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getPostThread.ts @@ -10,8 +10,11 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** Reference (AT-URI) to post record. */ uri: string + /** How many levels of reply depth should be included in response. */ depth: number + /** How many levels of parent (and grandparent, etc) post to include. */ parentHeight: number } diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getPosts.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getPosts.ts index 439e2132201..85000c74787 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getPosts.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getPosts.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** List of post AT-URIs to return hydrated views for. */ uris: string[] } diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getRepostedBy.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getRepostedBy.ts index 6dea2b753c7..40e008815d9 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getRepostedBy.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getRepostedBy.ts @@ -10,7 +10,9 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { + /** Reference (AT-URI) of post record */ uri: string + /** If supplied, filters to reposts of specific version (by CID) of the post record. */ cid?: string limit: number cursor?: string diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/getTimeline.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/getTimeline.ts index f37def5808e..5202c9eb6e3 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/getTimeline.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/getTimeline.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** Variant 'algorithm' for timeline. Implementation-specific. NOTE: most feed flexibility has been moved to feed generator mechanism. */ algorithm?: string limit: number cursor?: string diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/post.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/post.ts index 93870b4452d..c30825e118a 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/post.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/post.ts @@ -14,9 +14,11 @@ import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' export interface Record { + /** The primary post content. May be an empty string, if there are embeds. */ text: string - /** Deprecated: replaced by app.bsky.richtext.facet. */ + /** DEPRECATED: replaced by app.bsky.richtext.facet. */ entities?: Entity[] + /** Annotations of text (mentions, URLs, hashtags, etc) */ facets?: AppBskyRichtextFacet.Main[] reply?: ReplyRef embed?: @@ -25,12 +27,14 @@ export interface Record { | AppBskyEmbedRecord.Main | AppBskyEmbedRecordWithMedia.Main | { $type: string; [k: string]: unknown } + /** Indicates human language of post primary text content. */ langs?: string[] labels?: | ComAtprotoLabelDefs.SelfLabels | { $type: string; [k: string]: unknown } /** Additional non-inline tags describing this post. */ tags?: string[] + /** Client-declared timestamp when this post was originally created. */ createdAt: string [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/threadgate.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/threadgate.ts index 6a190d6e98a..e14140d5609 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/threadgate.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/threadgate.ts @@ -7,6 +7,7 @@ import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' export interface Record { + /** Reference (AT-URI) to the post record. */ post: string allow?: ( | MentionRule diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/block.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/block.ts index 947463af422..b7f19f126b7 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/block.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/block.ts @@ -7,6 +7,7 @@ import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' export interface Record { + /** DID of the account to be blocked. */ subject: string createdAt: string [k: string]: unknown diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getList.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getList.ts index 6b30cd7faa9..864a81b3833 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getList.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getList.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { + /** Reference (AT-URI) of the list record to hydrate. */ list: string limit: number cursor?: string diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getLists.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getLists.ts index 6bcb3134a47..dc0c4f18bea 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getLists.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getLists.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { + /** The account (actor) to enumerate lists from. */ actor: string limit: number cursor?: string diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/getRelationships.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/getRelationships.ts index 0125414ccd4..bd6b6e765ed 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/getRelationships.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/getRelationships.ts @@ -10,7 +10,9 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { + /** Primary account requesting relationships for. */ actor: string + /** List of 'other' accounts to be related back to the primary. */ others?: string[] } diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/list.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/list.ts index 36a7fb17a3f..91c8ccee5bb 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/list.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/list.ts @@ -11,6 +11,7 @@ import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface Record { purpose: AppBskyGraphDefs.ListPurpose + /** Display name for list; can not be empty. */ name: string description?: string descriptionFacets?: AppBskyRichtextFacet.Main[] diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/listblock.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/listblock.ts index 59f2e057eb5..592778c7a51 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/listblock.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/listblock.ts @@ -7,6 +7,7 @@ import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' export interface Record { + /** Reference (AT-URI) to the mod list record. */ subject: string createdAt: string [k: string]: unknown diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/listitem.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/listitem.ts index 69eff329ed4..5e93b34a111 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/graph/listitem.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/listitem.ts @@ -7,7 +7,9 @@ import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' export interface Record { + /** The account which is included on the list. */ subject: string + /** Reference (AT-URI) to the list record (app.bsky.graph.list). */ list: string createdAt: string [k: string]: unknown diff --git a/packages/bsky/src/lexicon/types/app/bsky/richtext/facet.ts b/packages/bsky/src/lexicon/types/app/bsky/richtext/facet.ts index 2c5b2d723a9..139b5382caf 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/richtext/facet.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/richtext/facet.ts @@ -6,6 +6,7 @@ import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' +/** Annotation of a sub-string within rich text. */ export interface Main { index: ByteSlice features: (Mention | Link | Tag | { $type: string; [k: string]: unknown })[] @@ -25,7 +26,7 @@ export function validateMain(v: unknown): ValidationResult { return lexicons.validate('app.bsky.richtext.facet#main', v) } -/** A facet feature for actor mentions. */ +/** Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID. */ export interface Mention { did: string [k: string]: unknown @@ -43,7 +44,7 @@ export function validateMention(v: unknown): ValidationResult { return lexicons.validate('app.bsky.richtext.facet#mention', v) } -/** A facet feature for links. */ +/** Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL. */ export interface Link { uri: string [k: string]: unknown @@ -61,7 +62,7 @@ export function validateLink(v: unknown): ValidationResult { return lexicons.validate('app.bsky.richtext.facet#link', v) } -/** A hashtag. */ +/** Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags'). */ export interface Tag { tag: string [k: string]: unknown @@ -77,7 +78,7 @@ export function validateTag(v: unknown): ValidationResult { return lexicons.validate('app.bsky.richtext.facet#tag', v) } -/** A text segment. Start is inclusive, end is exclusive. Indices are for utf8-encoded strings. */ +/** Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets. */ export interface ByteSlice { byteStart: number byteEnd: number diff --git a/packages/bsky/src/lexicon/types/com/atproto/identity/updateHandle.ts b/packages/bsky/src/lexicon/types/com/atproto/identity/updateHandle.ts index 6782a68ed54..f451d1f57c7 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/identity/updateHandle.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/identity/updateHandle.ts @@ -11,6 +11,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { + /** The new handle. */ handle: string [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/com/atproto/label/subscribeLabels.ts b/packages/bsky/src/lexicon/types/com/atproto/label/subscribeLabels.ts index 9d4b4441ae0..6034b35d895 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/label/subscribeLabels.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/label/subscribeLabels.ts @@ -10,7 +10,7 @@ import { IncomingMessage } from 'http' import * as ComAtprotoLabelDefs from './defs' export interface QueryParams { - /** The last known event to backfill from. */ + /** The last known event seq number to backfill from. */ cursor?: number } diff --git a/packages/bsky/src/lexicon/types/com/atproto/moderation/createReport.ts b/packages/bsky/src/lexicon/types/com/atproto/moderation/createReport.ts index c1335eb3d1f..aa3f810a91c 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/moderation/createReport.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/moderation/createReport.ts @@ -15,6 +15,7 @@ export interface QueryParams {} export interface InputSchema { reasonType: ComAtprotoModerationDefs.ReasonType + /** Additional context about the content and violation. */ reason?: string subject: | ComAtprotoAdminDefs.RepoRef diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/applyWrites.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/applyWrites.ts index 7bdb6dc2ed1..3956d7c3048 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/applyWrites.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/applyWrites.ts @@ -11,11 +11,12 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ + /** The handle or DID of the repo (aka, current account). */ repo: string - /** Flag for validating the records. */ + /** Can be set to 'false' to skip Lexicon schema validation of record data, for all operations. */ validate: boolean writes: (Create | Update | Delete)[] + /** If provided, the entire operation will fail if the current repo commit CID does not match this value. Used to prevent conflicting repo mutations. */ swapCommit?: string [k: string]: unknown } @@ -43,7 +44,7 @@ export type Handler = ( ctx: HandlerReqCtx, ) => Promise | HandlerOutput -/** Create a new record. */ +/** Operation which creates a new record. */ export interface Create { collection: string rkey?: string @@ -63,7 +64,7 @@ export function validateCreate(v: unknown): ValidationResult { return lexicons.validate('com.atproto.repo.applyWrites#create', v) } -/** Update an existing record. */ +/** Operation which updates an existing record. */ export interface Update { collection: string rkey: string @@ -83,7 +84,7 @@ export function validateUpdate(v: unknown): ValidationResult { return lexicons.validate('com.atproto.repo.applyWrites#update', v) } -/** Delete an existing record. */ +/** Operation which deletes an existing record. */ export interface Delete { collection: string rkey: string diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/createRecord.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/createRecord.ts index 666b91c828d..55cc95d0ad7 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/createRecord.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/createRecord.ts @@ -11,15 +11,15 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ + /** The handle or DID of the repo (aka, current account). */ repo: string /** The NSID of the record collection. */ collection: string - /** The key of the record. */ + /** The Record Key. */ rkey?: string - /** Flag for validating the record. */ + /** Can be set to 'false' to skip Lexicon schema validation of record data. */ validate: boolean - /** The record to create. */ + /** The record itself. Must contain a $type field. */ record: {} /** Compare and swap with the previous commit by CID. */ swapCommit?: string diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/deleteRecord.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/deleteRecord.ts index 65ee32d213d..3bb97be0aad 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/deleteRecord.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/deleteRecord.ts @@ -11,11 +11,11 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ + /** The handle or DID of the repo (aka, current account). */ repo: string /** The NSID of the record collection. */ collection: string - /** The key of the record. */ + /** The Record Key. */ rkey: string /** Compare and swap with the previous record by CID. */ swapRecord?: string diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/describeRepo.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/describeRepo.ts index 38b9c01ef1c..749bedcfeb7 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/describeRepo.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/describeRepo.ts @@ -18,8 +18,11 @@ export type InputSchema = undefined export interface OutputSchema { handle: string did: string + /** The complete DID document for this account. */ didDoc: {} + /** List of all the collections (NSIDs) for which this repo contains at least one record. */ collections: string[] + /** Indicates if handle is currently valid (resolves bi-directionally) */ handleIsCorrect: boolean [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/getRecord.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/getRecord.ts index 345dde29a53..1a737a848be 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/getRecord.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/getRecord.ts @@ -13,7 +13,7 @@ export interface QueryParams { repo: string /** The NSID of the record collection. */ collection: string - /** The key of the record. */ + /** The Record Key. */ rkey: string /** The CID of the version of the record. If not specified, then return the most recent version. */ cid?: string diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/putRecord.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/putRecord.ts index de93e2e9cf7..193841a2294 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/repo/putRecord.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/putRecord.ts @@ -11,17 +11,17 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ + /** The handle or DID of the repo (aka, current account). */ repo: string /** The NSID of the record collection. */ collection: string - /** The key of the record. */ + /** The Record Key. */ rkey: string - /** Flag for validating the record. */ + /** Can be set to 'false' to skip Lexicon schema validation of record data. */ validate: boolean /** The record to write. */ record: {} - /** Compare and swap with the previous record by CID. */ + /** Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation */ swapRecord?: string | null /** Compare and swap with the previous commit by CID. */ swapCommit?: string diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts b/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts index bceb61546cf..6e9b2f9f3c2 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/createAccount.ts @@ -12,22 +12,30 @@ export interface QueryParams {} export interface InputSchema { email?: string + /** Requested handle for the account. */ handle: string + /** Pre-existing atproto DID, being imported to a new account. */ did?: string inviteCode?: string verificationCode?: string verificationPhone?: string + /** Initial account password. May need to meet instance-specific password strength requirements. */ password?: string + /** DID PLC rotation key (aka, recovery key) to be included in PLC creation operation. */ recoveryKey?: string + /** A signed DID PLC operation to be submitted as part of importing an existing account to this instance. NOTE: this optional field may be updated when full account migration is implemented. */ plcOp?: {} [k: string]: unknown } +/** Account login session returned on successful account creation. */ export interface OutputSchema { accessJwt: string refreshJwt: string handle: string + /** The DID of the new account. */ did: string + /** Complete DID document. */ didDoc?: {} [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/createAppPassword.ts b/packages/bsky/src/lexicon/types/com/atproto/server/createAppPassword.ts index 474846546fe..dcc5178ecfa 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/createAppPassword.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/createAppPassword.ts @@ -11,6 +11,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { + /** A short name for the App Password, to help distinguish them. */ name: string [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/describeServer.ts b/packages/bsky/src/lexicon/types/com/atproto/server/describeServer.ts index 47be5b598b0..0e64c9d9708 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/describeServer.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/describeServer.ts @@ -13,8 +13,11 @@ export interface QueryParams {} export type InputSchema = undefined export interface OutputSchema { + /** If true, an invite code must be supplied to create an account on this instance. */ inviteCodeRequired?: boolean + /** If true, a phone verification token must be supplied to create an account on this instance. */ phoneVerificationRequired?: boolean + /** List of domain suffixes that can be used in account handles. */ availableUserDomains: string[] links?: Links [k: string]: unknown diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts b/packages/bsky/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts index 2dc551a477c..82c3ffa8c31 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts @@ -11,6 +11,7 @@ import * as ComAtprotoServerDefs from './defs' export interface QueryParams { includeUsed: boolean + /** Controls whether any new 'earned' but not 'created' invites should be created. */ createAvailable: boolean } diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/reserveSigningKey.ts b/packages/bsky/src/lexicon/types/com/atproto/server/reserveSigningKey.ts index 0de1220f4b5..0ec1e80c77c 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/reserveSigningKey.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/reserveSigningKey.ts @@ -11,13 +11,13 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** The did to reserve a new did:key for */ + /** The DID to reserve a key for. */ did?: string [k: string]: unknown } export interface OutputSchema { - /** Public signing key in the form of a did:key. */ + /** The public key for the reserved signing key, in did:key serialization. */ signingKey: string [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/getBlob.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/getBlob.ts index b3980fca500..93e50403f20 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/getBlob.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/getBlob.ts @@ -10,7 +10,7 @@ import { CID } from 'multiformats/cid' import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { - /** The DID of the repo. */ + /** The DID of the account. */ did: string /** The CID of the blob to fetch */ cid: string diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/getRecord.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/getRecord.ts index e27878ff5e6..c78ff8c2089 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/getRecord.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/getRecord.ts @@ -13,6 +13,7 @@ export interface QueryParams { /** The DID of the repo. */ did: string collection: string + /** Record Key */ rkey: string /** An optional past commit CID. */ commit?: string diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/getRepo.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/getRepo.ts index e0ad53ded7c..0d426557c5f 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/getRepo.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/getRepo.ts @@ -12,7 +12,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ did: string - /** The revision of the repo to catch up from. */ + /** The revision ('rev') of the repo to create a diff from. */ since?: string } diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/listRepos.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/listRepos.ts index 12532860895..e5a8e2ca9d6 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/listRepos.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/listRepos.ts @@ -48,6 +48,7 @@ export type Handler = ( export interface Repo { did: string + /** Current repo commit CID */ head: string rev: string [k: string]: unknown diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts index f9498e6691d..8a0af577c7c 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts @@ -11,7 +11,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** Hostname of the service that is notifying of update. */ + /** Hostname of the current service (usually a PDS) that is notifying of update. */ hostname: string [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/requestCrawl.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/requestCrawl.ts index 2859e28fe69..31180aabf58 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/requestCrawl.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/requestCrawl.ts @@ -11,7 +11,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** Hostname of the service that is requesting to be crawled. */ + /** Hostname of the current service (eg, PDS) that is requesting to be crawled. */ hostname: string [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/subscribeRepos.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/subscribeRepos.ts index fb334778bf6..689ea76daee 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/subscribeRepos.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/subscribeRepos.ts @@ -9,7 +9,7 @@ import { HandlerAuth, ErrorFrame } from '@atproto/xrpc-server' import { IncomingMessage } from 'http' export interface QueryParams { - /** The last known event to backfill from. */ + /** The last known event seq number to backfill from. */ cursor?: number } @@ -32,21 +32,29 @@ export type Handler = ( ctx: HandlerReqCtx, ) => AsyncIterable +/** Represents an update of repository state. Note that empty commits are allowed, which include no repo data changes, but an update to rev and signature. */ export interface Commit { + /** The stream sequence number of this message. */ seq: number + /** DEPRECATED -- unused */ rebase: boolean + /** Indicates that this commit contained too many ops, or data size was too large. Consumers will need to make a separate request to get missing data. */ tooBig: boolean + /** The repo this event comes from. */ repo: string + /** Repo commit object CID. */ commit: CID + /** DEPRECATED -- unused. WARNING -- nullable and optional; stick with optional to ensure golang interoperability. */ prev?: CID | null - /** The rev of the emitted commit. */ + /** The rev of the emitted commit. Note that this information is also in the commit object included in blocks, unless this is a tooBig event. */ rev: string - /** The rev of the last emitted commit from this repo. */ + /** The rev of the last emitted commit from this repo (if any). */ since: string | null - /** CAR file containing relevant blocks. */ + /** CAR file containing relevant blocks, as a diff since the previous repo state. */ blocks: Uint8Array ops: RepoOp[] blobs: CID[] + /** Timestamp of when this message was originally broadcast. */ time: string [k: string]: unknown } @@ -63,6 +71,7 @@ export function validateCommit(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#commit', v) } +/** Represents an update of the account's handle, or transition to/from invalid state. */ export interface Handle { seq: number did: string @@ -83,6 +92,7 @@ export function validateHandle(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#handle', v) } +/** Represents an account moving from one PDS instance to another. NOTE: not implemented; full account migration may introduce a new message instead. */ export interface Migrate { seq: number did: string @@ -103,6 +113,7 @@ export function validateMigrate(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#migrate', v) } +/** Indicates that an account has been deleted. */ export interface Tombstone { seq: number did: string @@ -140,10 +151,11 @@ export function validateInfo(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#info', v) } -/** A repo operation, ie a write of a single record. For creates and updates, CID is the record's CID as of this operation. For deletes, it's null. */ +/** A repo operation, ie a mutation of a single record. */ export interface RepoOp { action: 'create' | 'update' | 'delete' | (string & {}) path: string + /** For creates and updates, the new record CID. For deletions, null. */ cid: CID | null [k: string]: unknown } diff --git a/packages/ozone/src/lexicon/lexicons.ts b/packages/ozone/src/lexicon/lexicons.ts index 515098a8f49..8fcf9491077 100644 --- a/packages/ozone/src/lexicon/lexicons.ts +++ b/packages/ozone/src/lexicon/lexicons.ts @@ -1912,7 +1912,7 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Provides the DID of a repo.', + description: 'Resolves a handle (domain name) to a DID.', parameters: { type: 'params', required: ['handle'], @@ -1946,7 +1946,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Updates the handle of the account.', + description: + "Updates the current account's handle. Verifies handle validity, and updates did:plc document if necessary. Implemented by PDS, and requires auth.", input: { encoding: 'application/json', schema: { @@ -1956,6 +1957,7 @@ export const schemaDict = { handle: { type: 'string', format: 'handle', + description: 'The new handle.', }, }, }, @@ -2046,7 +2048,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Find labels relevant to the provided URI patterns.', + description: + 'Find labels relevant to the provided AT-URI patterns. Public endpoint for moderation services, though may return different or additional results with auth.', parameters: { type: 'params', required: ['uriPatterns'], @@ -2107,13 +2110,14 @@ export const schemaDict = { defs: { main: { type: 'subscription', - description: 'Subscribe to label updates.', + description: + 'Subscribe to stream of labels (and negations). Public endpoint implemented by mod services. Uses same sequencing scheme as repo event stream.', parameters: { type: 'params', properties: { cursor: { type: 'integer', - description: 'The last known event to backfill from.', + description: 'The last known event seq number to backfill from.', }, }, }, @@ -2169,7 +2173,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Report a repo or a record.', + description: + 'Submit a moderation report regarding an atproto account or record. Implemented by moderation services (with PDS proxying), and requires auth.', input: { encoding: 'application/json', schema: { @@ -2178,10 +2183,14 @@ export const schemaDict = { properties: { reasonType: { type: 'ref', + description: + 'Indicates the broad category of violation the report is for.', ref: 'lex:com.atproto.moderation.defs#reasonType', }, reason: { type: 'string', + description: + 'Additional context about the content and violation.', }, subject: { type: 'union', @@ -2292,7 +2301,7 @@ export const schemaDict = { main: { type: 'procedure', description: - 'Apply a batch transaction of creates, updates, and deletes.', + 'Apply a batch transaction of repository creates, updates, and deletes. Requires auth, implemented by PDS.', input: { encoding: 'application/json', schema: { @@ -2302,12 +2311,14 @@ export const schemaDict = { repo: { type: 'string', format: 'at-identifier', - description: 'The handle or DID of the repo.', + description: + 'The handle or DID of the repo (aka, current account).', }, validate: { type: 'boolean', default: true, - description: 'Flag for validating the records.', + description: + "Can be set to 'false' to skip Lexicon schema validation of record data, for all operations.", }, writes: { type: 'array', @@ -2323,6 +2334,8 @@ export const schemaDict = { }, swapCommit: { type: 'string', + description: + 'If provided, the entire operation will fail if the current repo commit CID does not match this value. Used to prevent conflicting repo mutations.', format: 'cid', }, }, @@ -2331,12 +2344,14 @@ export const schemaDict = { errors: [ { name: 'InvalidSwap', + description: + "Indicates that the 'swapCommit' parameter did not match current commit.", }, ], }, create: { type: 'object', - description: 'Create a new record.', + description: 'Operation which creates a new record.', required: ['collection', 'value'], properties: { collection: { @@ -2354,7 +2369,7 @@ export const schemaDict = { }, update: { type: 'object', - description: 'Update an existing record.', + description: 'Operation which updates an existing record.', required: ['collection', 'rkey', 'value'], properties: { collection: { @@ -2371,7 +2386,7 @@ export const schemaDict = { }, delete: { type: 'object', - description: 'Delete an existing record.', + description: 'Operation which deletes an existing record.', required: ['collection', 'rkey'], properties: { collection: { @@ -2391,7 +2406,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Create a new record.', + description: + 'Create a single new repository record. Requires auth, implemented by PDS.', input: { encoding: 'application/json', schema: { @@ -2401,7 +2417,8 @@ export const schemaDict = { repo: { type: 'string', format: 'at-identifier', - description: 'The handle or DID of the repo.', + description: + 'The handle or DID of the repo (aka, current account).', }, collection: { type: 'string', @@ -2410,17 +2427,18 @@ export const schemaDict = { }, rkey: { type: 'string', - description: 'The key of the record.', + description: 'The Record Key.', maxLength: 15, }, validate: { type: 'boolean', default: true, - description: 'Flag for validating the record.', + description: + "Can be set to 'false' to skip Lexicon schema validation of record data.", }, record: { type: 'unknown', - description: 'The record to create.', + description: 'The record itself. Must contain a $type field.', }, swapCommit: { type: 'string', @@ -2451,6 +2469,8 @@ export const schemaDict = { errors: [ { name: 'InvalidSwap', + description: + "Indicates that 'swapCommit' didn't match current repo commit.", }, ], }, @@ -2462,7 +2482,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: "Delete a record, or ensure it doesn't exist.", + description: + "Delete a repository record, or ensure it doesn't exist. Requires auth, implemented by PDS.", input: { encoding: 'application/json', schema: { @@ -2472,7 +2493,8 @@ export const schemaDict = { repo: { type: 'string', format: 'at-identifier', - description: 'The handle or DID of the repo.', + description: + 'The handle or DID of the repo (aka, current account).', }, collection: { type: 'string', @@ -2481,7 +2503,7 @@ export const schemaDict = { }, rkey: { type: 'string', - description: 'The key of the record.', + description: 'The Record Key.', }, swapRecord: { type: 'string', @@ -2513,7 +2535,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Get information about the repo, including the list of collections.', + 'Get information about an account and repository, including the list of collections. Does not require auth.', parameters: { type: 'params', required: ['repo'], @@ -2547,9 +2569,12 @@ export const schemaDict = { }, didDoc: { type: 'unknown', + description: 'The complete DID document for this account.', }, collections: { type: 'array', + description: + 'List of all the collections (NSIDs) for which this repo contains at least one record.', items: { type: 'string', format: 'nsid', @@ -2557,6 +2582,8 @@ export const schemaDict = { }, handleIsCorrect: { type: 'boolean', + description: + 'Indicates if handle is currently valid (resolves bi-directionally)', }, }, }, @@ -2570,7 +2597,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a record.', + description: + 'Get a single record from a repository. Does not require auth.', parameters: { type: 'params', required: ['repo', 'collection', 'rkey'], @@ -2587,7 +2615,7 @@ export const schemaDict = { }, rkey: { type: 'string', - description: 'The key of the record.', + description: 'The Record Key.', }, cid: { type: 'string', @@ -2626,7 +2654,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'List a range of records in a collection.', + description: + 'List a range of records in a repository, matching a specific collection. Does not require auth.', parameters: { type: 'params', required: ['repo', 'collection'], @@ -2712,7 +2741,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Write a record, creating or updating it as needed.', + description: + 'Write a repository record, creating or updating it as needed. Requires auth, implemented by PDS.', input: { encoding: 'application/json', schema: { @@ -2723,7 +2753,8 @@ export const schemaDict = { repo: { type: 'string', format: 'at-identifier', - description: 'The handle or DID of the repo.', + description: + 'The handle or DID of the repo (aka, current account).', }, collection: { type: 'string', @@ -2732,13 +2763,14 @@ export const schemaDict = { }, rkey: { type: 'string', - description: 'The key of the record.', + description: 'The Record Key.', maxLength: 15, }, validate: { type: 'boolean', default: true, - description: 'Flag for validating the record.', + description: + "Can be set to 'false' to skip Lexicon schema validation of record data.", }, record: { type: 'unknown', @@ -2748,7 +2780,7 @@ export const schemaDict = { type: 'string', format: 'cid', description: - 'Compare and swap with the previous record by CID.', + 'Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation', }, swapCommit: { type: 'string', @@ -2812,7 +2844,7 @@ export const schemaDict = { main: { type: 'procedure', description: - 'Upload a new blob to be added to repo in a later request.', + 'Upload a new blob, to be referenced from a repository record. The blob will be deleted if it is not referenced within a time window (eg, minutes). Blob restrictions (mimetype, size, etc) are enforced when the reference is created. Requires auth, implemented by PDS.', input: { encoding: '*/*', }, @@ -2877,7 +2909,7 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Create an account.', + description: 'Create an account. Implemented by PDS.', input: { encoding: 'application/json', schema: { @@ -2890,10 +2922,13 @@ export const schemaDict = { handle: { type: 'string', format: 'handle', + description: 'Requested handle for the account.', }, did: { type: 'string', format: 'did', + description: + 'Pre-existing atproto DID, being imported to a new account.', }, inviteCode: { type: 'string', @@ -2906,12 +2941,18 @@ export const schemaDict = { }, password: { type: 'string', + description: + 'Initial account password. May need to meet instance-specific password strength requirements.', }, recoveryKey: { type: 'string', + description: + 'DID PLC rotation key (aka, recovery key) to be included in PLC creation operation.', }, plcOp: { type: 'unknown', + description: + 'A signed DID PLC operation to be submitted as part of importing an existing account to this instance. NOTE: this optional field may be updated when full account migration is implemented.', }, }, }, @@ -2920,6 +2961,8 @@ export const schemaDict = { encoding: 'application/json', schema: { type: 'object', + description: + 'Account login session returned on successful account creation.', required: ['accessJwt', 'refreshJwt', 'handle', 'did'], properties: { accessJwt: { @@ -2935,9 +2978,11 @@ export const schemaDict = { did: { type: 'string', format: 'did', + description: 'The DID of the new account.', }, didDoc: { type: 'unknown', + description: 'Complete DID document.', }, }, }, @@ -2983,6 +3028,8 @@ export const schemaDict = { properties: { name: { type: 'string', + description: + 'A short name for the App Password, to help distinguish them.', }, }, }, @@ -3250,7 +3297,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: "Delete an actor's account with a token and password.", + description: + "Delete an actor's account with a token and password. Can only be called after requesting a deletion token. Requires auth.", input: { encoding: 'application/json', schema: { @@ -3287,7 +3335,7 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Delete the current session.', + description: 'Delete the current session. Requires auth.', }, }, }, @@ -3298,7 +3346,7 @@ export const schemaDict = { main: { type: 'query', description: - "Get a document describing the service's accounts configuration.", + "Describes the server's account creation requirements and capabilities. Implemented by PDS.", output: { encoding: 'application/json', schema: { @@ -3307,18 +3355,25 @@ export const schemaDict = { properties: { inviteCodeRequired: { type: 'boolean', + description: + 'If true, an invite code must be supplied to create an account on this instance.', }, phoneVerificationRequired: { type: 'boolean', + description: + 'If true, a phone verification token must be supplied to create an account on this instance.', }, availableUserDomains: { type: 'array', + description: + 'List of domain suffixes that can be used in account handles.', items: { type: 'string', }, }, links: { type: 'ref', + description: 'URLs of service policy documents.', ref: 'lex:com.atproto.server.describeServer#links', }, }, @@ -3344,7 +3399,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get all invite codes for a given account.', + description: + 'Get all invite codes for the current account. Requires auth.', parameters: { type: 'params', properties: { @@ -3355,6 +3411,8 @@ export const schemaDict = { createAvailable: { type: 'boolean', default: true, + description: + "Controls whether any new 'earned' but not 'created' invites should be created.", }, }, }, @@ -3388,7 +3446,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get information about the current session.', + description: + 'Get information about the current auth session. Requires auth.', output: { encoding: 'application/json', schema: { @@ -3468,7 +3527,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Refresh an authentication session.', + description: + "Refresh an authentication session. Requires auth using the 'refreshJwt' (not the 'accessJwt').", output: { encoding: 'application/json', schema: { @@ -3574,7 +3634,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Reserve a repo signing key for account creation.', + description: + 'Reserve a repo signing key, for use with account creation. Necessary so that a DID PLC update operation can be constructed during an account migraiton. Public and does not require auth; implemented by PDS. NOTE: this endpoint may change when full account migration is implemented.', input: { encoding: 'application/json', schema: { @@ -3582,7 +3643,7 @@ export const schemaDict = { properties: { did: { type: 'string', - description: 'The did to reserve a new did:key for', + description: 'The DID to reserve a key for.', }, }, }, @@ -3595,7 +3656,8 @@ export const schemaDict = { properties: { signingKey: { type: 'string', - description: 'Public signing key in the form of a did:key.', + description: + 'The public key for the reserved signing key, in did:key serialization.', }, }, }, @@ -3702,7 +3764,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a blob associated with a given repo.', + description: + 'Get a blob associated with a given account. Returns the full blob as originally uploaded. Does not require auth; implemented by PDS.', parameters: { type: 'params', required: ['did', 'cid'], @@ -3710,7 +3773,7 @@ export const schemaDict = { did: { type: 'string', format: 'did', - description: 'The DID of the repo.', + description: 'The DID of the account.', }, cid: { type: 'string', @@ -3731,7 +3794,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get blocks from a given repo.', + description: + 'Get data blocks from a given repo, by CID. For example, intermediate MST nodes, or records. Does not require auth; implemented by PDS.', parameters: { type: 'params', required: ['did', 'cids'], @@ -3826,7 +3890,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get the current commit CID & revision of the repo.', + description: + 'Get the current commit CID & revision of the specified repo. Does not require auth.', parameters: { type: 'params', required: ['did'], @@ -3869,7 +3934,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Get blocks needed for existence or non-existence of record.', + 'Get data blocks needed to prove the existence or non-existence of record in the current version of repo. Does not require auth.', parameters: { type: 'params', required: ['did', 'collection', 'rkey'], @@ -3885,6 +3950,7 @@ export const schemaDict = { }, rkey: { type: 'string', + description: 'Record Key', }, commit: { type: 'string', @@ -3906,7 +3972,7 @@ export const schemaDict = { main: { type: 'query', description: - "Gets the DID's repo, optionally catching up from a specific revision.", + "Download a repository export as CAR file. Optionally only a 'diff' since a previous revision. Does not require auth; implemented by PDS.", parameters: { type: 'params', required: ['did'], @@ -3918,7 +3984,8 @@ export const schemaDict = { }, since: { type: 'string', - description: 'The revision of the repo to catch up from.', + description: + "The revision ('rev') of the repo to create a diff from.", }, }, }, @@ -3934,7 +4001,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'List blob CIDs since some revision.', + description: + 'List blob CIDso for an account, since some repo revision. Does not require auth; implemented by PDS.', parameters: { type: 'params', required: ['did'], @@ -3987,7 +4055,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'List DIDs and root CIDs of hosted repos.', + description: + 'Enumerates all the DID, rev, and commit CID for all repos hosted by this service. Does not require auth; implemented by PDS and Relay.', parameters: { type: 'params', properties: { @@ -4033,6 +4102,7 @@ export const schemaDict = { head: { type: 'string', format: 'cid', + description: 'Current repo commit CID', }, rev: { type: 'string', @@ -4048,7 +4118,7 @@ export const schemaDict = { main: { type: 'procedure', description: - 'Notify a crawling service of a recent update; often when a long break between updates causes the connection with the crawling service to break.', + 'Notify a crawling service of a recent update, and that crawling should resume. Intended use is after a gap between repo stream events caused the crawling service to disconnect. Does not require auth; implemented by Relay.', input: { encoding: 'application/json', schema: { @@ -4058,7 +4128,7 @@ export const schemaDict = { hostname: { type: 'string', description: - 'Hostname of the service that is notifying of update.', + 'Hostname of the current service (usually a PDS) that is notifying of update.', }, }, }, @@ -4072,7 +4142,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Request a service to persistently crawl hosted repos.', + description: + 'Request a service to persistently crawl hosted repos. Expected use is new PDS instances declaring their existence to Relays. Does not require auth.', input: { encoding: 'application/json', schema: { @@ -4082,7 +4153,7 @@ export const schemaDict = { hostname: { type: 'string', description: - 'Hostname of the service that is requesting to be crawled.', + 'Hostname of the current service (eg, PDS) that is requesting to be crawled.', }, }, }, @@ -4096,13 +4167,14 @@ export const schemaDict = { defs: { main: { type: 'subscription', - description: 'Subscribe to repo updates.', + description: + 'Repository event stream, aka Firehose endpoint. Outputs repo commits with diff data, and identity update events, for all repositories on the current server. See the atproto specifications for details around stream sequencing, repo versioning, CAR diff format, and more. Public and does not require auth; implemented by PDS and Relay.', parameters: { type: 'params', properties: { cursor: { type: 'integer', - description: 'The last known event to backfill from.', + description: 'The last known event seq number to backfill from.', }, }, }, @@ -4124,11 +4196,15 @@ export const schemaDict = { }, { name: 'ConsumerTooSlow', + description: + 'If the consumer of the stream can not keep up with events, and a backlog gets too large, the server will drop the connection.', }, ], }, commit: { type: 'object', + description: + 'Represents an update of repository state. Note that empty commits are allowed, which include no repo data changes, but an update to rev and signature.', required: [ 'seq', 'rebase', @@ -4146,34 +4222,45 @@ export const schemaDict = { properties: { seq: { type: 'integer', + description: 'The stream sequence number of this message.', }, rebase: { type: 'boolean', + description: 'DEPRECATED -- unused', }, tooBig: { type: 'boolean', + description: + 'Indicates that this commit contained too many ops, or data size was too large. Consumers will need to make a separate request to get missing data.', }, repo: { type: 'string', format: 'did', + description: 'The repo this event comes from.', }, commit: { type: 'cid-link', + description: 'Repo commit object CID.', }, prev: { type: 'cid-link', + description: + 'DEPRECATED -- unused. WARNING -- nullable and optional; stick with optional to ensure golang interoperability.', }, rev: { type: 'string', - description: 'The rev of the emitted commit.', + description: + 'The rev of the emitted commit. Note that this information is also in the commit object included in blocks, unless this is a tooBig event.', }, since: { type: 'string', - description: 'The rev of the last emitted commit from this repo.', + description: + 'The rev of the last emitted commit from this repo (if any).', }, blocks: { type: 'bytes', - description: 'CAR file containing relevant blocks.', + description: + 'CAR file containing relevant blocks, as a diff since the previous repo state.', maxLength: 1000000, }, ops: { @@ -4181,6 +4268,8 @@ export const schemaDict = { items: { type: 'ref', ref: 'lex:com.atproto.sync.subscribeRepos#repoOp', + description: + 'List of repo mutation operations in this commit (eg, records created, updated, or deleted).', }, maxLength: 200, }, @@ -4188,16 +4277,22 @@ export const schemaDict = { type: 'array', items: { type: 'cid-link', + description: + 'List of new blobs (by CID) referenced by records in this commit.', }, }, time: { type: 'string', format: 'datetime', + description: + 'Timestamp of when this message was originally broadcast.', }, }, }, handle: { type: 'object', + description: + "Represents an update of the account's handle, or transition to/from invalid state.", required: ['seq', 'did', 'handle', 'time'], properties: { seq: { @@ -4219,6 +4314,8 @@ export const schemaDict = { }, migrate: { type: 'object', + description: + 'Represents an account moving from one PDS instance to another. NOTE: not implemented; full account migration may introduce a new message instead.', required: ['seq', 'did', 'migrateTo', 'time'], nullable: ['migrateTo'], properties: { @@ -4240,6 +4337,7 @@ export const schemaDict = { }, tombstone: { type: 'object', + description: 'Indicates that an account has been deleted.', required: ['seq', 'did', 'time'], properties: { seq: { @@ -4270,8 +4368,7 @@ export const schemaDict = { }, repoOp: { type: 'object', - description: - "A repo operation, ie a write of a single record. For creates and updates, CID is the record's CID as of this operation. For deletes, it's null.", + description: 'A repo operation, ie a mutation of a single record.', required: ['action', 'path', 'cid'], nullable: ['cid'], properties: { @@ -4284,6 +4381,8 @@ export const schemaDict = { }, cid: { type: 'cid-link', + description: + 'For creates and updates, the new record CID. For deletions, null.', }, }, }, @@ -4324,7 +4423,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Fetch all labels from a labeler created after a certain date.', + 'Fetch all labels from a labeler created after a certain date. DEPRECATED: use queryLabels or subscribeLabels instead', parameters: { type: 'params', properties: { @@ -4440,7 +4539,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Transfer an account.', + description: + 'Transfer an account. NOTE: temporary method, necessarily how account migration will be implemented.', input: { encoding: 'application/json', schema: { @@ -4513,7 +4613,6 @@ export const schemaDict = { AppBskyActorDefs: { lexicon: 1, id: 'app.bsky.actor.defs', - description: 'A reference to an actor in the network.', defs: { profileViewBasic: { type: 'object', @@ -4646,6 +4745,8 @@ export const schemaDict = { }, viewerState: { type: 'object', + description: + "Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests.", properties: { muted: { type: 'boolean', @@ -4815,7 +4916,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get private preferences attached to the account.', + description: + 'Get private preferences attached to the current account. Expected use is synchronization between multiple devices, and import/export during account migration. Requires auth.', parameters: { type: 'params', properties: {}, @@ -4842,7 +4944,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get detailed profile view of an actor.', + description: + 'Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth.', parameters: { type: 'params', required: ['actor'], @@ -4850,6 +4953,7 @@ export const schemaDict = { actor: { type: 'string', format: 'at-identifier', + description: 'Handle or DID of account to fetch profile of.', }, }, }, @@ -4909,7 +5013,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of suggested actors, used for discovery.', + description: + 'Get a list of suggested actors. Expected use is discovery of accounts to follow during new account onboarding.', parameters: { type: 'params', properties: { @@ -4952,7 +5057,7 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a profile.', + description: 'A declaration of a Bluesky account profile.', key: 'literal:self', record: { type: 'object', @@ -4964,21 +5069,28 @@ export const schemaDict = { }, description: { type: 'string', + description: 'Free-form profile description text.', maxGraphemes: 256, maxLength: 2560, }, avatar: { type: 'blob', + description: + "Small image to be displayed next to posts from account. AKA, 'profile picture'", accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, banner: { type: 'blob', + description: + 'Larger horizontal image to display behind profile view.', accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, labels: { type: 'union', + description: + 'Self-label values, specific to the Bluesky application, on the overall account.', refs: ['lex:com.atproto.label.defs#selfLabels'], }, }, @@ -5015,7 +5127,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Find actors (profiles) matching search criteria.', + description: + 'Find actors (profiles) matching search criteria. Does not require auth.', parameters: { type: 'params', properties: { @@ -5067,7 +5180,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Find actor suggestions for a prefix search term.', + description: + 'Find actor suggestions for a prefix search term. Expected use is for auto-completion during text field entry. Does not require auth.', parameters: { type: 'params', properties: { @@ -5109,11 +5223,11 @@ export const schemaDict = { AppBskyEmbedExternal: { lexicon: 1, id: 'app.bsky.embed.external', - description: - 'A representation of some externally linked content, embedded in another form of content.', defs: { main: { type: 'object', + description: + "A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post).", required: ['external'], properties: { external: { @@ -5177,7 +5291,7 @@ export const schemaDict = { AppBskyEmbedImages: { lexicon: 1, id: 'app.bsky.embed.images', - description: 'A set of images embedded in some other form of content.', + description: 'A set of images embedded in a Bluesky record (eg, a post).', defs: { main: { type: 'object', @@ -5204,6 +5318,8 @@ export const schemaDict = { }, alt: { type: 'string', + description: + 'Alt text description of the image, for accessibility.', }, aspectRatio: { type: 'ref', @@ -5247,12 +5363,18 @@ export const schemaDict = { properties: { thumb: { type: 'string', + description: + 'Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View.', }, fullsize: { type: 'string', + description: + 'Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View.', }, alt: { type: 'string', + description: + 'Alt text description of the image, for accessibility.', }, aspectRatio: { type: 'ref', @@ -5266,7 +5388,7 @@ export const schemaDict = { lexicon: 1, id: 'app.bsky.embed.record', description: - 'A representation of a record embedded in another form of content.', + 'A representation of a record embedded in a Bluesky record (eg, a post). For example, a quote-post, or sharing a feed generator record.', defs: { main: { type: 'object', @@ -5312,6 +5434,7 @@ export const schemaDict = { }, value: { type: 'unknown', + description: 'The record data itself.', }, labels: { type: 'array', @@ -5376,7 +5499,7 @@ export const schemaDict = { lexicon: 1, id: 'app.bsky.embed.recordWithMedia', description: - 'A representation of a record embedded in another form of content, alongside other compatible embeds.', + 'A representation of a record embedded in a Bluesky record (eg, a post), alongside other compatible embeds. For example, a quote post and image, or a quote post and external URL card.', defs: { main: { type: 'object', @@ -5475,6 +5598,8 @@ export const schemaDict = { }, viewerState: { type: 'object', + description: + "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests.", properties: { repost: { type: 'string', @@ -5735,7 +5860,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Get information about a feed generator, including policies and offered feed URIs.', + 'Get information about a feed generator, including policies and offered feed URIs. Does not require auth; implemented by Feed Generator services (not App View).', output: { encoding: 'application/json', schema: { @@ -5790,7 +5915,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of the existence of a feed generator.', + description: + 'Record declaring of the existence of a feed generator, and containing metadata about it. The record can exist in any repository.', key: 'any', record: { type: 'object', @@ -5824,6 +5950,7 @@ export const schemaDict = { }, labels: { type: 'union', + description: 'Self-label values', refs: ['lex:com.atproto.label.defs#selfLabels'], }, createdAt: { @@ -5841,7 +5968,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of feeds created by the actor.', + description: + "Get a list of feeds (feed generator records) created by the actor (in the actor's repo).", parameters: { type: 'params', required: ['actor'], @@ -5889,7 +6017,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of posts liked by an actor.', + description: + 'Get a list of posts liked by an actor. Does not require auth.', parameters: { type: 'params', required: ['actor'], @@ -5945,7 +6074,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: "Get a view of an actor's feed.", + description: + "Get a view of an actor's 'author feed' (post and reposts by the author). Does not require auth.", parameters: { type: 'params', required: ['actor'], @@ -5965,6 +6095,8 @@ export const schemaDict = { }, filter: { type: 'string', + description: + 'Combinations of post/repost types to include in response.', knownValues: [ 'posts_with_replies', 'posts_no_replies', @@ -6012,7 +6144,7 @@ export const schemaDict = { main: { type: 'query', description: - "Get a hydrated feed from an actor's selected feed generator.", + "Get a hydrated feed from an actor's selected feed generator. Implemented by App View.", parameters: { type: 'params', required: ['feed'], @@ -6065,7 +6197,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get information about a feed generator.', + description: + 'Get information about a feed generator. Implemented by AppView.', parameters: { type: 'params', required: ['feed'], @@ -6073,6 +6206,7 @@ export const schemaDict = { feed: { type: 'string', format: 'at-uri', + description: 'AT-URI of the feed generator record.', }, }, }, @@ -6088,9 +6222,13 @@ export const schemaDict = { }, isOnline: { type: 'boolean', + description: + 'Indicates whether the feed generator service has been online recently, or else seems to be inactive.', }, isValid: { type: 'boolean', + description: + 'Indicates whether the feed generator service is compatible with the record declaration.', }, }, }, @@ -6143,7 +6281,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a skeleton of a feed provided by a feed generator.', + description: + 'Get a skeleton of a feed provided by a feed generator. Auth is optional, depending on provider requirements, and provides the DID of the requester. Implemented by Feed Generator Service.', parameters: { type: 'params', required: ['feed'], @@ -6151,6 +6290,8 @@ export const schemaDict = { feed: { type: 'string', format: 'at-uri', + description: + 'Reference to feed generator record describing the specific feed being requested.', }, limit: { type: 'integer', @@ -6196,7 +6337,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get the list of likes.', + description: + 'Get like records which reference a subject (by AT-URI and CID).', parameters: { type: 'params', required: ['uri'], @@ -6204,10 +6346,13 @@ export const schemaDict = { uri: { type: 'string', format: 'at-uri', + description: 'AT-URI of the subject (eg, a post record).', }, cid: { type: 'string', format: 'cid', + description: + 'CID of the subject record (aka, specific version of record), to filter likes.', }, limit: { type: 'integer', @@ -6274,7 +6419,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a view of a recent posts from actors in a list.', + description: + 'Get a feed of recent posts from a list (posts and reposts from any actors on the list). Does not require auth.', parameters: { type: 'params', required: ['list'], @@ -6282,6 +6428,7 @@ export const schemaDict = { list: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) to the list record.', }, limit: { type: 'integer', @@ -6327,7 +6474,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get posts in a thread.', + description: + 'Get posts in a thread. Does not require auth, but additional metadata and filtering will be applied for authed requests.', parameters: { type: 'params', required: ['uri'], @@ -6335,15 +6483,20 @@ export const schemaDict = { uri: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) to post record.', }, depth: { type: 'integer', + description: + 'How many levels of reply depth should be included in response.', default: 6, minimum: 0, maximum: 1000, }, parentHeight: { type: 'integer', + description: + 'How many levels of parent (and grandparent, etc) post to include.', default: 80, minimum: 0, maximum: 1000, @@ -6381,13 +6534,15 @@ export const schemaDict = { defs: { main: { type: 'query', - description: "Get a view of an actor's feed.", + description: + "Gets post views for a specified list of posts (by AT-URI). This is sometimes referred to as 'hydrating' a 'feed skeleton'.", parameters: { type: 'params', required: ['uris'], properties: { uris: { type: 'array', + description: 'List of post AT-URIs to return hydrated views for.', items: { type: 'string', format: 'at-uri', @@ -6421,7 +6576,7 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of reposts.', + description: 'Get a list of reposts for a given post.', parameters: { type: 'params', required: ['uri'], @@ -6429,10 +6584,13 @@ export const schemaDict = { uri: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) of post record', }, cid: { type: 'string', format: 'cid', + description: + 'If supplied, filters to reposts of specific version (by CID) of the post record.', }, limit: { type: 'integer', @@ -6481,7 +6639,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of suggested feeds for the viewer.', + description: + 'Get a list of suggested feeds (feed generators) for the requesting account.', parameters: { type: 'params', properties: { @@ -6524,12 +6683,15 @@ export const schemaDict = { defs: { main: { type: 'query', - description: "Get a view of the actor's home timeline.", + description: + "Get a view of the requesting account's home timeline. This is expected to be some form of reverse-chronological feed.", parameters: { type: 'params', properties: { algorithm: { type: 'string', + description: + "Variant 'algorithm' for timeline. Implementation-specific. NOTE: most feed flexibility has been moved to feed generator mechanism.", }, limit: { type: 'integer', @@ -6570,7 +6732,7 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a like.', + description: "Record declaring a 'like' of a piece of subject content.", key: 'tid', record: { type: 'object', @@ -6595,7 +6757,7 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a post.', + description: 'Record containing a Bluesky post.', key: 'tid', record: { type: 'object', @@ -6605,10 +6767,12 @@ export const schemaDict = { type: 'string', maxLength: 3000, maxGraphemes: 300, + description: + 'The primary post content. May be an empty string, if there are embeds.', }, entities: { type: 'array', - description: 'Deprecated: replaced by app.bsky.richtext.facet.', + description: 'DEPRECATED: replaced by app.bsky.richtext.facet.', items: { type: 'ref', ref: 'lex:app.bsky.feed.post#entity', @@ -6616,6 +6780,8 @@ export const schemaDict = { }, facets: { type: 'array', + description: + 'Annotations of text (mentions, URLs, hashtags, etc)', items: { type: 'ref', ref: 'lex:app.bsky.richtext.facet', @@ -6636,6 +6802,8 @@ export const schemaDict = { }, langs: { type: 'array', + description: + 'Indicates human language of post primary text content.', maxLength: 3, items: { type: 'string', @@ -6644,21 +6812,25 @@ export const schemaDict = { }, labels: { type: 'union', + description: + 'Self-label values for this post. Effectively content warnings.', refs: ['lex:com.atproto.label.defs#selfLabels'], }, tags: { type: 'array', + description: 'Additional non-inline tags describing this post.', maxLength: 8, items: { type: 'string', maxLength: 640, maxGraphemes: 64, }, - description: 'Additional non-inline tags describing this post.', }, createdAt: { type: 'string', format: 'datetime', + description: + 'Client-declared timestamp when this post was originally created.', }, }, }, @@ -6718,7 +6890,8 @@ export const schemaDict = { id: 'app.bsky.feed.repost', defs: { main: { - description: 'A declaration of a repost.', + description: + "Record representing a 'repost' of an existing Bluesky post.", type: 'record', key: 'tid', record: { @@ -6744,7 +6917,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Find posts matching search criteria.', + description: + 'Find posts matching search criteria, returning views of those posts.', parameters: { type: 'params', required: ['q'], @@ -6807,7 +6981,7 @@ export const schemaDict = { type: 'record', key: 'tid', description: - "Defines interaction gating rules for a thread. The rkey of the threadgate record should match the rkey of the thread's root post.", + "Record defining interaction gating rules for a thread (aka, reply controls). The record key (rkey) of the threadgate record must match the record key of the thread's root post, and that record must be in the same repository..", record: { type: 'object', required: ['post', 'createdAt'], @@ -6815,6 +6989,7 @@ export const schemaDict = { post: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) to the post record.', }, allow: { type: 'array', @@ -6864,7 +7039,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a block.', + description: + "Record declaring a 'block' relationship against another account. NOTE: blocks are public in Bluesky; see blog posts for details.", key: 'tid', record: { type: 'object', @@ -6873,6 +7049,7 @@ export const schemaDict = { subject: { type: 'string', format: 'did', + description: 'DID of the account to be blocked.', }, createdAt: { type: 'string', @@ -7061,7 +7238,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a social follow.', + description: + "Record declaring a social 'follow' relationship of another account. Duplicate follows will be ignored by the AppView.", key: 'tid', record: { type: 'object', @@ -7086,7 +7264,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of who the actor is blocking.', + description: + 'Enumerates which accounts the requesting account is currently blocking. Requires auth.', parameters: { type: 'params', properties: { @@ -7129,7 +7308,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: "Get a list of an actor's followers.", + description: + 'Enumerates accounts which follow a specified account (actor).', parameters: { type: 'params', required: ['actor'], @@ -7181,7 +7361,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of who the actor follows.', + description: + 'Enumerates accounts which a specified account (actor) follows.', parameters: { type: 'params', required: ['actor'], @@ -7233,7 +7414,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of actors.', + description: + "Gets a 'view' (with additional context) of a specified list.", parameters: { type: 'params', required: ['list'], @@ -7241,6 +7423,7 @@ export const schemaDict = { list: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) of the list record to hydrate.', }, limit: { type: 'integer', @@ -7285,7 +7468,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get lists that the actor is blocking.', + description: + 'Get mod lists that the requesting account (actor) is blocking. Requires auth.', parameters: { type: 'params', properties: { @@ -7328,7 +7512,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get lists that the actor is muting.', + description: + 'Enumerates mod lists that the requesting account (actor) currently has muted. Requires auth.', parameters: { type: 'params', properties: { @@ -7371,7 +7556,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of lists that belong to an actor.', + description: + 'Enumerates the lists created by a specified account (actor).', parameters: { type: 'params', required: ['actor'], @@ -7379,6 +7565,7 @@ export const schemaDict = { actor: { type: 'string', format: 'at-identifier', + description: 'The account (actor) to enumerate lists from.', }, limit: { type: 'integer', @@ -7419,7 +7606,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of who the actor mutes.', + description: + 'Enumerates accounts that the requesting account (actor) currently has muted. Requires auth.', parameters: { type: 'params', properties: { @@ -7463,7 +7651,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Enumerates public relationships between one account, and a list of other accounts', + 'Enumerates public relationships between one account, and a list of other accounts. Does not require auth.', parameters: { type: 'params', required: ['actor'], @@ -7471,9 +7659,12 @@ export const schemaDict = { actor: { type: 'string', format: 'at-identifier', + description: 'Primary account requesting relationships for.', }, others: { type: 'array', + description: + "List of 'other' accounts to be related back to the primary.", maxLength: 30, items: { type: 'string', @@ -7521,7 +7712,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get suggested follows related to a given actor.', + description: + 'Enumerates follows similar to a given account (actor). Expected use is to recommend additional accounts immediately after following one account.', parameters: { type: 'params', required: ['actor'], @@ -7557,7 +7749,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a list of actors.', + description: + 'Record representing a list of accounts (actors). Scope includes both moderation-oriented lists and curration-oriented lists.', key: 'tid', record: { type: 'object', @@ -7565,12 +7758,15 @@ export const schemaDict = { properties: { purpose: { type: 'ref', + description: + 'Defines the purpose of the list (aka, moderation-oriented or curration-oriented)', ref: 'lex:app.bsky.graph.defs#listPurpose', }, name: { type: 'string', maxLength: 64, minLength: 1, + description: 'Display name for list; can not be empty.', }, description: { type: 'string', @@ -7608,7 +7804,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A block of an entire list of actors.', + description: + 'Record representing a block relationship against an entire an entire list of accounts (actors).', key: 'tid', record: { type: 'object', @@ -7617,6 +7814,7 @@ export const schemaDict = { subject: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) to the mod list record.', }, createdAt: { type: 'string', @@ -7633,7 +7831,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'An item under a declared list of actors.', + description: + "Record representing an account's inclusion on a specific list. The AppView will ignore duplicate listitem records.", key: 'tid', record: { type: 'object', @@ -7642,10 +7841,13 @@ export const schemaDict = { subject: { type: 'string', format: 'did', + description: 'The account which is included on the list.', }, list: { type: 'string', format: 'at-uri', + description: + 'Reference (AT-URI) to the list record (app.bsky.graph.list).', }, createdAt: { type: 'string', @@ -7662,7 +7864,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Mute an actor by DID or handle.', + description: + 'Creates a mute relationship for the specified account. Mutes are private in Bluesky. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7685,7 +7888,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Mute a list of actors.', + description: + 'Creates a mute relationship for the specified list of accounts. Mutes are private in Bluesky. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7708,7 +7912,7 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Unmute an actor by DID or handle.', + description: 'Unmutes the specified account. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7731,7 +7935,7 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Unmute a list of actors.', + description: 'Unmutes the specified list of accounts. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7754,7 +7958,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get the count of unread notifications.', + description: + 'Count the number of unread notifications for the requesting account. Requires auth.', parameters: { type: 'params', properties: { @@ -7785,7 +7990,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of notifications.', + description: + 'Enumerate notifications for the requesting account. Requires auth.', parameters: { type: 'params', properties: { @@ -7896,7 +8102,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Register for push notifications with a service.', + description: + 'Register to receive push notifications, via a specified service, for the requesting account. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7929,7 +8136,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Notify server that the user has seen notifications.', + description: + 'Notify server that the requesting account has seen notifications. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7952,6 +8160,7 @@ export const schemaDict = { defs: { main: { type: 'object', + description: 'Annotation of a sub-string within rich text.', required: ['index', 'features'], properties: { index: { @@ -7973,7 +8182,8 @@ export const schemaDict = { }, mention: { type: 'object', - description: 'A facet feature for actor mentions.', + description: + "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID.", required: ['did'], properties: { did: { @@ -7984,7 +8194,8 @@ export const schemaDict = { }, link: { type: 'object', - description: 'A facet feature for links.', + description: + 'Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL.', required: ['uri'], properties: { uri: { @@ -7995,7 +8206,8 @@ export const schemaDict = { }, tag: { type: 'object', - description: 'A hashtag.', + description: + "Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags').", required: ['tag'], properties: { tag: { @@ -8008,7 +8220,7 @@ export const schemaDict = { byteSlice: { type: 'object', description: - 'A text segment. Start is inclusive, end is exclusive. Indices are for utf8-encoded strings.', + 'Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets.', required: ['byteStart', 'byteEnd'], properties: { byteStart: { diff --git a/packages/ozone/src/lexicon/types/app/bsky/actor/defs.ts b/packages/ozone/src/lexicon/types/app/bsky/actor/defs.ts index 8cdcafcb72f..ff49c40dc0b 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/actor/defs.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/actor/defs.ts @@ -82,6 +82,7 @@ export function validateProfileViewDetailed(v: unknown): ValidationResult { return lexicons.validate('app.bsky.actor.defs#profileViewDetailed', v) } +/** Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests. */ export interface ViewerState { muted?: boolean mutedByList?: AppBskyGraphDefs.ListViewBasic diff --git a/packages/ozone/src/lexicon/types/app/bsky/actor/getProfile.ts b/packages/ozone/src/lexicon/types/app/bsky/actor/getProfile.ts index be58c73a233..5a7b1f25bfc 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/actor/getProfile.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/actor/getProfile.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams { + /** Handle or DID of account to fetch profile of. */ actor: string } diff --git a/packages/ozone/src/lexicon/types/app/bsky/actor/profile.ts b/packages/ozone/src/lexicon/types/app/bsky/actor/profile.ts index 7dbc4c1ccec..8810ce7bed9 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/actor/profile.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/actor/profile.ts @@ -9,8 +9,11 @@ import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface Record { displayName?: string + /** Free-form profile description text. */ description?: string + /** Small image to be displayed next to posts from account. AKA, 'profile picture' */ avatar?: BlobRef + /** Larger horizontal image to display behind profile view. */ banner?: BlobRef labels?: | ComAtprotoLabelDefs.SelfLabels diff --git a/packages/ozone/src/lexicon/types/app/bsky/embed/external.ts b/packages/ozone/src/lexicon/types/app/bsky/embed/external.ts index f42a6cfd95c..b137ee4b6f5 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/embed/external.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/embed/external.ts @@ -6,6 +6,7 @@ import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' +/** A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post). */ export interface Main { external: External [k: string]: unknown diff --git a/packages/ozone/src/lexicon/types/app/bsky/embed/images.ts b/packages/ozone/src/lexicon/types/app/bsky/embed/images.ts index 4864fad3dea..96399867a1a 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/embed/images.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/embed/images.ts @@ -26,6 +26,7 @@ export function validateMain(v: unknown): ValidationResult { export interface Image { image: BlobRef + /** Alt text description of the image, for accessibility. */ alt: string aspectRatio?: AspectRatio [k: string]: unknown @@ -76,8 +77,11 @@ export function validateView(v: unknown): ValidationResult { } export interface ViewImage { + /** Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View. */ thumb: string + /** Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View. */ fullsize: string + /** Alt text description of the image, for accessibility. */ alt: string aspectRatio?: AspectRatio [k: string]: unknown diff --git a/packages/ozone/src/lexicon/types/app/bsky/embed/record.ts b/packages/ozone/src/lexicon/types/app/bsky/embed/record.ts index cea5742a45e..dbe7f13152b 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/embed/record.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/embed/record.ts @@ -57,6 +57,7 @@ export interface ViewRecord { uri: string cid: string author: AppBskyActorDefs.ProfileViewBasic + /** The record data itself. */ value: {} labels?: ComAtprotoLabelDefs.Label[] embeds?: ( diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/defs.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/defs.ts index 382d3f58ecf..261d8a622ec 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/defs.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/defs.ts @@ -45,6 +45,7 @@ export function validatePostView(v: unknown): ValidationResult { return lexicons.validate('app.bsky.feed.defs#postView', v) } +/** Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests. */ export interface ViewerState { repost?: string like?: string diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts index 8f8e038c71f..017c7a6a2d4 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts @@ -13,6 +13,7 @@ export interface QueryParams { actor: string limit: number cursor?: string + /** Combinations of post/repost types to include in response. */ filter: | 'posts_with_replies' | 'posts_no_replies' diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts index b65a5151d46..7ab89057a8c 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** AT-URI of the feed generator record. */ feed: string } @@ -17,7 +18,9 @@ export type InputSchema = undefined export interface OutputSchema { view: AppBskyFeedDefs.GeneratorView + /** Indicates whether the feed generator service has been online recently, or else seems to be inactive. */ isOnline: boolean + /** Indicates whether the feed generator service is compatible with the record declaration. */ isValid: boolean [k: string]: unknown } diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts index ab1911ecb87..ca1cef20f08 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** Reference to feed generator record describing the specific feed being requested. */ feed: string limit: number cursor?: string diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getLikes.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getLikes.ts index c7e8c860bd6..275d99bba3d 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getLikes.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getLikes.ts @@ -10,7 +10,9 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { + /** AT-URI of the subject (eg, a post record). */ uri: string + /** CID of the subject record (aka, specific version of record), to filter likes. */ cid?: string limit: number cursor?: string diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getListFeed.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getListFeed.ts index ab157788f72..84e12deaa92 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getListFeed.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getListFeed.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** Reference (AT-URI) to the list record. */ list: string limit: number cursor?: string diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getPostThread.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getPostThread.ts index 9e81da0eff6..ae232fd91a2 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getPostThread.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getPostThread.ts @@ -10,8 +10,11 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** Reference (AT-URI) to post record. */ uri: string + /** How many levels of reply depth should be included in response. */ depth: number + /** How many levels of parent (and grandparent, etc) post to include. */ parentHeight: number } diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getPosts.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getPosts.ts index 439e2132201..85000c74787 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getPosts.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getPosts.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** List of post AT-URIs to return hydrated views for. */ uris: string[] } diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getRepostedBy.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getRepostedBy.ts index 6dea2b753c7..40e008815d9 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getRepostedBy.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getRepostedBy.ts @@ -10,7 +10,9 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { + /** Reference (AT-URI) of post record */ uri: string + /** If supplied, filters to reposts of specific version (by CID) of the post record. */ cid?: string limit: number cursor?: string diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/getTimeline.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/getTimeline.ts index f37def5808e..5202c9eb6e3 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/getTimeline.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/getTimeline.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** Variant 'algorithm' for timeline. Implementation-specific. NOTE: most feed flexibility has been moved to feed generator mechanism. */ algorithm?: string limit: number cursor?: string diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/post.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/post.ts index 93870b4452d..c30825e118a 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/post.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/post.ts @@ -14,9 +14,11 @@ import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' export interface Record { + /** The primary post content. May be an empty string, if there are embeds. */ text: string - /** Deprecated: replaced by app.bsky.richtext.facet. */ + /** DEPRECATED: replaced by app.bsky.richtext.facet. */ entities?: Entity[] + /** Annotations of text (mentions, URLs, hashtags, etc) */ facets?: AppBskyRichtextFacet.Main[] reply?: ReplyRef embed?: @@ -25,12 +27,14 @@ export interface Record { | AppBskyEmbedRecord.Main | AppBskyEmbedRecordWithMedia.Main | { $type: string; [k: string]: unknown } + /** Indicates human language of post primary text content. */ langs?: string[] labels?: | ComAtprotoLabelDefs.SelfLabels | { $type: string; [k: string]: unknown } /** Additional non-inline tags describing this post. */ tags?: string[] + /** Client-declared timestamp when this post was originally created. */ createdAt: string [k: string]: unknown } diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/threadgate.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/threadgate.ts index 6a190d6e98a..e14140d5609 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/threadgate.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/threadgate.ts @@ -7,6 +7,7 @@ import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' export interface Record { + /** Reference (AT-URI) to the post record. */ post: string allow?: ( | MentionRule diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/block.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/block.ts index 947463af422..b7f19f126b7 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/block.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/block.ts @@ -7,6 +7,7 @@ import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' export interface Record { + /** DID of the account to be blocked. */ subject: string createdAt: string [k: string]: unknown diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/getList.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/getList.ts index 6b30cd7faa9..864a81b3833 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/getList.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/getList.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { + /** Reference (AT-URI) of the list record to hydrate. */ list: string limit: number cursor?: string diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/getLists.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/getLists.ts index 6bcb3134a47..dc0c4f18bea 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/getLists.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/getLists.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { + /** The account (actor) to enumerate lists from. */ actor: string limit: number cursor?: string diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/getRelationships.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/getRelationships.ts index 0125414ccd4..bd6b6e765ed 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/getRelationships.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/getRelationships.ts @@ -10,7 +10,9 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { + /** Primary account requesting relationships for. */ actor: string + /** List of 'other' accounts to be related back to the primary. */ others?: string[] } diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/list.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/list.ts index 36a7fb17a3f..91c8ccee5bb 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/list.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/list.ts @@ -11,6 +11,7 @@ import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface Record { purpose: AppBskyGraphDefs.ListPurpose + /** Display name for list; can not be empty. */ name: string description?: string descriptionFacets?: AppBskyRichtextFacet.Main[] diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/listblock.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/listblock.ts index 59f2e057eb5..592778c7a51 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/listblock.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/listblock.ts @@ -7,6 +7,7 @@ import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' export interface Record { + /** Reference (AT-URI) to the mod list record. */ subject: string createdAt: string [k: string]: unknown diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/listitem.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/listitem.ts index 69eff329ed4..5e93b34a111 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/graph/listitem.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/listitem.ts @@ -7,7 +7,9 @@ import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' export interface Record { + /** The account which is included on the list. */ subject: string + /** Reference (AT-URI) to the list record (app.bsky.graph.list). */ list: string createdAt: string [k: string]: unknown diff --git a/packages/ozone/src/lexicon/types/app/bsky/richtext/facet.ts b/packages/ozone/src/lexicon/types/app/bsky/richtext/facet.ts index 2c5b2d723a9..139b5382caf 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/richtext/facet.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/richtext/facet.ts @@ -6,6 +6,7 @@ import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' +/** Annotation of a sub-string within rich text. */ export interface Main { index: ByteSlice features: (Mention | Link | Tag | { $type: string; [k: string]: unknown })[] @@ -25,7 +26,7 @@ export function validateMain(v: unknown): ValidationResult { return lexicons.validate('app.bsky.richtext.facet#main', v) } -/** A facet feature for actor mentions. */ +/** Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID. */ export interface Mention { did: string [k: string]: unknown @@ -43,7 +44,7 @@ export function validateMention(v: unknown): ValidationResult { return lexicons.validate('app.bsky.richtext.facet#mention', v) } -/** A facet feature for links. */ +/** Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL. */ export interface Link { uri: string [k: string]: unknown @@ -61,7 +62,7 @@ export function validateLink(v: unknown): ValidationResult { return lexicons.validate('app.bsky.richtext.facet#link', v) } -/** A hashtag. */ +/** Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags'). */ export interface Tag { tag: string [k: string]: unknown @@ -77,7 +78,7 @@ export function validateTag(v: unknown): ValidationResult { return lexicons.validate('app.bsky.richtext.facet#tag', v) } -/** A text segment. Start is inclusive, end is exclusive. Indices are for utf8-encoded strings. */ +/** Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets. */ export interface ByteSlice { byteStart: number byteEnd: number diff --git a/packages/ozone/src/lexicon/types/com/atproto/identity/updateHandle.ts b/packages/ozone/src/lexicon/types/com/atproto/identity/updateHandle.ts index 6782a68ed54..f451d1f57c7 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/identity/updateHandle.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/identity/updateHandle.ts @@ -11,6 +11,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { + /** The new handle. */ handle: string [k: string]: unknown } diff --git a/packages/ozone/src/lexicon/types/com/atproto/label/subscribeLabels.ts b/packages/ozone/src/lexicon/types/com/atproto/label/subscribeLabels.ts index 9d4b4441ae0..6034b35d895 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/label/subscribeLabels.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/label/subscribeLabels.ts @@ -10,7 +10,7 @@ import { IncomingMessage } from 'http' import * as ComAtprotoLabelDefs from './defs' export interface QueryParams { - /** The last known event to backfill from. */ + /** The last known event seq number to backfill from. */ cursor?: number } diff --git a/packages/ozone/src/lexicon/types/com/atproto/moderation/createReport.ts b/packages/ozone/src/lexicon/types/com/atproto/moderation/createReport.ts index c1335eb3d1f..aa3f810a91c 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/moderation/createReport.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/moderation/createReport.ts @@ -15,6 +15,7 @@ export interface QueryParams {} export interface InputSchema { reasonType: ComAtprotoModerationDefs.ReasonType + /** Additional context about the content and violation. */ reason?: string subject: | ComAtprotoAdminDefs.RepoRef diff --git a/packages/ozone/src/lexicon/types/com/atproto/repo/applyWrites.ts b/packages/ozone/src/lexicon/types/com/atproto/repo/applyWrites.ts index 7bdb6dc2ed1..3956d7c3048 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/repo/applyWrites.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/repo/applyWrites.ts @@ -11,11 +11,12 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ + /** The handle or DID of the repo (aka, current account). */ repo: string - /** Flag for validating the records. */ + /** Can be set to 'false' to skip Lexicon schema validation of record data, for all operations. */ validate: boolean writes: (Create | Update | Delete)[] + /** If provided, the entire operation will fail if the current repo commit CID does not match this value. Used to prevent conflicting repo mutations. */ swapCommit?: string [k: string]: unknown } @@ -43,7 +44,7 @@ export type Handler = ( ctx: HandlerReqCtx, ) => Promise | HandlerOutput -/** Create a new record. */ +/** Operation which creates a new record. */ export interface Create { collection: string rkey?: string @@ -63,7 +64,7 @@ export function validateCreate(v: unknown): ValidationResult { return lexicons.validate('com.atproto.repo.applyWrites#create', v) } -/** Update an existing record. */ +/** Operation which updates an existing record. */ export interface Update { collection: string rkey: string @@ -83,7 +84,7 @@ export function validateUpdate(v: unknown): ValidationResult { return lexicons.validate('com.atproto.repo.applyWrites#update', v) } -/** Delete an existing record. */ +/** Operation which deletes an existing record. */ export interface Delete { collection: string rkey: string diff --git a/packages/ozone/src/lexicon/types/com/atproto/repo/createRecord.ts b/packages/ozone/src/lexicon/types/com/atproto/repo/createRecord.ts index 666b91c828d..55cc95d0ad7 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/repo/createRecord.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/repo/createRecord.ts @@ -11,15 +11,15 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ + /** The handle or DID of the repo (aka, current account). */ repo: string /** The NSID of the record collection. */ collection: string - /** The key of the record. */ + /** The Record Key. */ rkey?: string - /** Flag for validating the record. */ + /** Can be set to 'false' to skip Lexicon schema validation of record data. */ validate: boolean - /** The record to create. */ + /** The record itself. Must contain a $type field. */ record: {} /** Compare and swap with the previous commit by CID. */ swapCommit?: string diff --git a/packages/ozone/src/lexicon/types/com/atproto/repo/deleteRecord.ts b/packages/ozone/src/lexicon/types/com/atproto/repo/deleteRecord.ts index 65ee32d213d..3bb97be0aad 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/repo/deleteRecord.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/repo/deleteRecord.ts @@ -11,11 +11,11 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ + /** The handle or DID of the repo (aka, current account). */ repo: string /** The NSID of the record collection. */ collection: string - /** The key of the record. */ + /** The Record Key. */ rkey: string /** Compare and swap with the previous record by CID. */ swapRecord?: string diff --git a/packages/ozone/src/lexicon/types/com/atproto/repo/describeRepo.ts b/packages/ozone/src/lexicon/types/com/atproto/repo/describeRepo.ts index 38b9c01ef1c..749bedcfeb7 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/repo/describeRepo.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/repo/describeRepo.ts @@ -18,8 +18,11 @@ export type InputSchema = undefined export interface OutputSchema { handle: string did: string + /** The complete DID document for this account. */ didDoc: {} + /** List of all the collections (NSIDs) for which this repo contains at least one record. */ collections: string[] + /** Indicates if handle is currently valid (resolves bi-directionally) */ handleIsCorrect: boolean [k: string]: unknown } diff --git a/packages/ozone/src/lexicon/types/com/atproto/repo/getRecord.ts b/packages/ozone/src/lexicon/types/com/atproto/repo/getRecord.ts index 345dde29a53..1a737a848be 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/repo/getRecord.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/repo/getRecord.ts @@ -13,7 +13,7 @@ export interface QueryParams { repo: string /** The NSID of the record collection. */ collection: string - /** The key of the record. */ + /** The Record Key. */ rkey: string /** The CID of the version of the record. If not specified, then return the most recent version. */ cid?: string diff --git a/packages/ozone/src/lexicon/types/com/atproto/repo/putRecord.ts b/packages/ozone/src/lexicon/types/com/atproto/repo/putRecord.ts index de93e2e9cf7..193841a2294 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/repo/putRecord.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/repo/putRecord.ts @@ -11,17 +11,17 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ + /** The handle or DID of the repo (aka, current account). */ repo: string /** The NSID of the record collection. */ collection: string - /** The key of the record. */ + /** The Record Key. */ rkey: string - /** Flag for validating the record. */ + /** Can be set to 'false' to skip Lexicon schema validation of record data. */ validate: boolean /** The record to write. */ record: {} - /** Compare and swap with the previous record by CID. */ + /** Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation */ swapRecord?: string | null /** Compare and swap with the previous commit by CID. */ swapCommit?: string diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/createAccount.ts b/packages/ozone/src/lexicon/types/com/atproto/server/createAccount.ts index bceb61546cf..6e9b2f9f3c2 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/createAccount.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/createAccount.ts @@ -12,22 +12,30 @@ export interface QueryParams {} export interface InputSchema { email?: string + /** Requested handle for the account. */ handle: string + /** Pre-existing atproto DID, being imported to a new account. */ did?: string inviteCode?: string verificationCode?: string verificationPhone?: string + /** Initial account password. May need to meet instance-specific password strength requirements. */ password?: string + /** DID PLC rotation key (aka, recovery key) to be included in PLC creation operation. */ recoveryKey?: string + /** A signed DID PLC operation to be submitted as part of importing an existing account to this instance. NOTE: this optional field may be updated when full account migration is implemented. */ plcOp?: {} [k: string]: unknown } +/** Account login session returned on successful account creation. */ export interface OutputSchema { accessJwt: string refreshJwt: string handle: string + /** The DID of the new account. */ did: string + /** Complete DID document. */ didDoc?: {} [k: string]: unknown } diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/createAppPassword.ts b/packages/ozone/src/lexicon/types/com/atproto/server/createAppPassword.ts index 474846546fe..dcc5178ecfa 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/createAppPassword.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/createAppPassword.ts @@ -11,6 +11,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { + /** A short name for the App Password, to help distinguish them. */ name: string [k: string]: unknown } diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/describeServer.ts b/packages/ozone/src/lexicon/types/com/atproto/server/describeServer.ts index 47be5b598b0..0e64c9d9708 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/describeServer.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/describeServer.ts @@ -13,8 +13,11 @@ export interface QueryParams {} export type InputSchema = undefined export interface OutputSchema { + /** If true, an invite code must be supplied to create an account on this instance. */ inviteCodeRequired?: boolean + /** If true, a phone verification token must be supplied to create an account on this instance. */ phoneVerificationRequired?: boolean + /** List of domain suffixes that can be used in account handles. */ availableUserDomains: string[] links?: Links [k: string]: unknown diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts b/packages/ozone/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts index 2dc551a477c..82c3ffa8c31 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts @@ -11,6 +11,7 @@ import * as ComAtprotoServerDefs from './defs' export interface QueryParams { includeUsed: boolean + /** Controls whether any new 'earned' but not 'created' invites should be created. */ createAvailable: boolean } diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/reserveSigningKey.ts b/packages/ozone/src/lexicon/types/com/atproto/server/reserveSigningKey.ts index 0de1220f4b5..0ec1e80c77c 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/reserveSigningKey.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/reserveSigningKey.ts @@ -11,13 +11,13 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** The did to reserve a new did:key for */ + /** The DID to reserve a key for. */ did?: string [k: string]: unknown } export interface OutputSchema { - /** Public signing key in the form of a did:key. */ + /** The public key for the reserved signing key, in did:key serialization. */ signingKey: string [k: string]: unknown } diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/getBlob.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/getBlob.ts index b3980fca500..93e50403f20 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/getBlob.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/getBlob.ts @@ -10,7 +10,7 @@ import { CID } from 'multiformats/cid' import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { - /** The DID of the repo. */ + /** The DID of the account. */ did: string /** The CID of the blob to fetch */ cid: string diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/getRecord.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/getRecord.ts index e27878ff5e6..c78ff8c2089 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/getRecord.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/getRecord.ts @@ -13,6 +13,7 @@ export interface QueryParams { /** The DID of the repo. */ did: string collection: string + /** Record Key */ rkey: string /** An optional past commit CID. */ commit?: string diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/getRepo.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/getRepo.ts index e0ad53ded7c..0d426557c5f 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/getRepo.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/getRepo.ts @@ -12,7 +12,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ did: string - /** The revision of the repo to catch up from. */ + /** The revision ('rev') of the repo to create a diff from. */ since?: string } diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/listRepos.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/listRepos.ts index 12532860895..e5a8e2ca9d6 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/listRepos.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/listRepos.ts @@ -48,6 +48,7 @@ export type Handler = ( export interface Repo { did: string + /** Current repo commit CID */ head: string rev: string [k: string]: unknown diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts index f9498e6691d..8a0af577c7c 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts @@ -11,7 +11,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** Hostname of the service that is notifying of update. */ + /** Hostname of the current service (usually a PDS) that is notifying of update. */ hostname: string [k: string]: unknown } diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/requestCrawl.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/requestCrawl.ts index 2859e28fe69..31180aabf58 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/requestCrawl.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/requestCrawl.ts @@ -11,7 +11,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** Hostname of the service that is requesting to be crawled. */ + /** Hostname of the current service (eg, PDS) that is requesting to be crawled. */ hostname: string [k: string]: unknown } diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/subscribeRepos.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/subscribeRepos.ts index fb334778bf6..689ea76daee 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/subscribeRepos.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/subscribeRepos.ts @@ -9,7 +9,7 @@ import { HandlerAuth, ErrorFrame } from '@atproto/xrpc-server' import { IncomingMessage } from 'http' export interface QueryParams { - /** The last known event to backfill from. */ + /** The last known event seq number to backfill from. */ cursor?: number } @@ -32,21 +32,29 @@ export type Handler = ( ctx: HandlerReqCtx, ) => AsyncIterable +/** Represents an update of repository state. Note that empty commits are allowed, which include no repo data changes, but an update to rev and signature. */ export interface Commit { + /** The stream sequence number of this message. */ seq: number + /** DEPRECATED -- unused */ rebase: boolean + /** Indicates that this commit contained too many ops, or data size was too large. Consumers will need to make a separate request to get missing data. */ tooBig: boolean + /** The repo this event comes from. */ repo: string + /** Repo commit object CID. */ commit: CID + /** DEPRECATED -- unused. WARNING -- nullable and optional; stick with optional to ensure golang interoperability. */ prev?: CID | null - /** The rev of the emitted commit. */ + /** The rev of the emitted commit. Note that this information is also in the commit object included in blocks, unless this is a tooBig event. */ rev: string - /** The rev of the last emitted commit from this repo. */ + /** The rev of the last emitted commit from this repo (if any). */ since: string | null - /** CAR file containing relevant blocks. */ + /** CAR file containing relevant blocks, as a diff since the previous repo state. */ blocks: Uint8Array ops: RepoOp[] blobs: CID[] + /** Timestamp of when this message was originally broadcast. */ time: string [k: string]: unknown } @@ -63,6 +71,7 @@ export function validateCommit(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#commit', v) } +/** Represents an update of the account's handle, or transition to/from invalid state. */ export interface Handle { seq: number did: string @@ -83,6 +92,7 @@ export function validateHandle(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#handle', v) } +/** Represents an account moving from one PDS instance to another. NOTE: not implemented; full account migration may introduce a new message instead. */ export interface Migrate { seq: number did: string @@ -103,6 +113,7 @@ export function validateMigrate(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#migrate', v) } +/** Indicates that an account has been deleted. */ export interface Tombstone { seq: number did: string @@ -140,10 +151,11 @@ export function validateInfo(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#info', v) } -/** A repo operation, ie a write of a single record. For creates and updates, CID is the record's CID as of this operation. For deletes, it's null. */ +/** A repo operation, ie a mutation of a single record. */ export interface RepoOp { action: 'create' | 'update' | 'delete' | (string & {}) path: string + /** For creates and updates, the new record CID. For deletions, null. */ cid: CID | null [k: string]: unknown } diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index 515098a8f49..8fcf9491077 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -1912,7 +1912,7 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Provides the DID of a repo.', + description: 'Resolves a handle (domain name) to a DID.', parameters: { type: 'params', required: ['handle'], @@ -1946,7 +1946,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Updates the handle of the account.', + description: + "Updates the current account's handle. Verifies handle validity, and updates did:plc document if necessary. Implemented by PDS, and requires auth.", input: { encoding: 'application/json', schema: { @@ -1956,6 +1957,7 @@ export const schemaDict = { handle: { type: 'string', format: 'handle', + description: 'The new handle.', }, }, }, @@ -2046,7 +2048,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Find labels relevant to the provided URI patterns.', + description: + 'Find labels relevant to the provided AT-URI patterns. Public endpoint for moderation services, though may return different or additional results with auth.', parameters: { type: 'params', required: ['uriPatterns'], @@ -2107,13 +2110,14 @@ export const schemaDict = { defs: { main: { type: 'subscription', - description: 'Subscribe to label updates.', + description: + 'Subscribe to stream of labels (and negations). Public endpoint implemented by mod services. Uses same sequencing scheme as repo event stream.', parameters: { type: 'params', properties: { cursor: { type: 'integer', - description: 'The last known event to backfill from.', + description: 'The last known event seq number to backfill from.', }, }, }, @@ -2169,7 +2173,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Report a repo or a record.', + description: + 'Submit a moderation report regarding an atproto account or record. Implemented by moderation services (with PDS proxying), and requires auth.', input: { encoding: 'application/json', schema: { @@ -2178,10 +2183,14 @@ export const schemaDict = { properties: { reasonType: { type: 'ref', + description: + 'Indicates the broad category of violation the report is for.', ref: 'lex:com.atproto.moderation.defs#reasonType', }, reason: { type: 'string', + description: + 'Additional context about the content and violation.', }, subject: { type: 'union', @@ -2292,7 +2301,7 @@ export const schemaDict = { main: { type: 'procedure', description: - 'Apply a batch transaction of creates, updates, and deletes.', + 'Apply a batch transaction of repository creates, updates, and deletes. Requires auth, implemented by PDS.', input: { encoding: 'application/json', schema: { @@ -2302,12 +2311,14 @@ export const schemaDict = { repo: { type: 'string', format: 'at-identifier', - description: 'The handle or DID of the repo.', + description: + 'The handle or DID of the repo (aka, current account).', }, validate: { type: 'boolean', default: true, - description: 'Flag for validating the records.', + description: + "Can be set to 'false' to skip Lexicon schema validation of record data, for all operations.", }, writes: { type: 'array', @@ -2323,6 +2334,8 @@ export const schemaDict = { }, swapCommit: { type: 'string', + description: + 'If provided, the entire operation will fail if the current repo commit CID does not match this value. Used to prevent conflicting repo mutations.', format: 'cid', }, }, @@ -2331,12 +2344,14 @@ export const schemaDict = { errors: [ { name: 'InvalidSwap', + description: + "Indicates that the 'swapCommit' parameter did not match current commit.", }, ], }, create: { type: 'object', - description: 'Create a new record.', + description: 'Operation which creates a new record.', required: ['collection', 'value'], properties: { collection: { @@ -2354,7 +2369,7 @@ export const schemaDict = { }, update: { type: 'object', - description: 'Update an existing record.', + description: 'Operation which updates an existing record.', required: ['collection', 'rkey', 'value'], properties: { collection: { @@ -2371,7 +2386,7 @@ export const schemaDict = { }, delete: { type: 'object', - description: 'Delete an existing record.', + description: 'Operation which deletes an existing record.', required: ['collection', 'rkey'], properties: { collection: { @@ -2391,7 +2406,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Create a new record.', + description: + 'Create a single new repository record. Requires auth, implemented by PDS.', input: { encoding: 'application/json', schema: { @@ -2401,7 +2417,8 @@ export const schemaDict = { repo: { type: 'string', format: 'at-identifier', - description: 'The handle or DID of the repo.', + description: + 'The handle or DID of the repo (aka, current account).', }, collection: { type: 'string', @@ -2410,17 +2427,18 @@ export const schemaDict = { }, rkey: { type: 'string', - description: 'The key of the record.', + description: 'The Record Key.', maxLength: 15, }, validate: { type: 'boolean', default: true, - description: 'Flag for validating the record.', + description: + "Can be set to 'false' to skip Lexicon schema validation of record data.", }, record: { type: 'unknown', - description: 'The record to create.', + description: 'The record itself. Must contain a $type field.', }, swapCommit: { type: 'string', @@ -2451,6 +2469,8 @@ export const schemaDict = { errors: [ { name: 'InvalidSwap', + description: + "Indicates that 'swapCommit' didn't match current repo commit.", }, ], }, @@ -2462,7 +2482,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: "Delete a record, or ensure it doesn't exist.", + description: + "Delete a repository record, or ensure it doesn't exist. Requires auth, implemented by PDS.", input: { encoding: 'application/json', schema: { @@ -2472,7 +2493,8 @@ export const schemaDict = { repo: { type: 'string', format: 'at-identifier', - description: 'The handle or DID of the repo.', + description: + 'The handle or DID of the repo (aka, current account).', }, collection: { type: 'string', @@ -2481,7 +2503,7 @@ export const schemaDict = { }, rkey: { type: 'string', - description: 'The key of the record.', + description: 'The Record Key.', }, swapRecord: { type: 'string', @@ -2513,7 +2535,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Get information about the repo, including the list of collections.', + 'Get information about an account and repository, including the list of collections. Does not require auth.', parameters: { type: 'params', required: ['repo'], @@ -2547,9 +2569,12 @@ export const schemaDict = { }, didDoc: { type: 'unknown', + description: 'The complete DID document for this account.', }, collections: { type: 'array', + description: + 'List of all the collections (NSIDs) for which this repo contains at least one record.', items: { type: 'string', format: 'nsid', @@ -2557,6 +2582,8 @@ export const schemaDict = { }, handleIsCorrect: { type: 'boolean', + description: + 'Indicates if handle is currently valid (resolves bi-directionally)', }, }, }, @@ -2570,7 +2597,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a record.', + description: + 'Get a single record from a repository. Does not require auth.', parameters: { type: 'params', required: ['repo', 'collection', 'rkey'], @@ -2587,7 +2615,7 @@ export const schemaDict = { }, rkey: { type: 'string', - description: 'The key of the record.', + description: 'The Record Key.', }, cid: { type: 'string', @@ -2626,7 +2654,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'List a range of records in a collection.', + description: + 'List a range of records in a repository, matching a specific collection. Does not require auth.', parameters: { type: 'params', required: ['repo', 'collection'], @@ -2712,7 +2741,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Write a record, creating or updating it as needed.', + description: + 'Write a repository record, creating or updating it as needed. Requires auth, implemented by PDS.', input: { encoding: 'application/json', schema: { @@ -2723,7 +2753,8 @@ export const schemaDict = { repo: { type: 'string', format: 'at-identifier', - description: 'The handle or DID of the repo.', + description: + 'The handle or DID of the repo (aka, current account).', }, collection: { type: 'string', @@ -2732,13 +2763,14 @@ export const schemaDict = { }, rkey: { type: 'string', - description: 'The key of the record.', + description: 'The Record Key.', maxLength: 15, }, validate: { type: 'boolean', default: true, - description: 'Flag for validating the record.', + description: + "Can be set to 'false' to skip Lexicon schema validation of record data.", }, record: { type: 'unknown', @@ -2748,7 +2780,7 @@ export const schemaDict = { type: 'string', format: 'cid', description: - 'Compare and swap with the previous record by CID.', + 'Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation', }, swapCommit: { type: 'string', @@ -2812,7 +2844,7 @@ export const schemaDict = { main: { type: 'procedure', description: - 'Upload a new blob to be added to repo in a later request.', + 'Upload a new blob, to be referenced from a repository record. The blob will be deleted if it is not referenced within a time window (eg, minutes). Blob restrictions (mimetype, size, etc) are enforced when the reference is created. Requires auth, implemented by PDS.', input: { encoding: '*/*', }, @@ -2877,7 +2909,7 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Create an account.', + description: 'Create an account. Implemented by PDS.', input: { encoding: 'application/json', schema: { @@ -2890,10 +2922,13 @@ export const schemaDict = { handle: { type: 'string', format: 'handle', + description: 'Requested handle for the account.', }, did: { type: 'string', format: 'did', + description: + 'Pre-existing atproto DID, being imported to a new account.', }, inviteCode: { type: 'string', @@ -2906,12 +2941,18 @@ export const schemaDict = { }, password: { type: 'string', + description: + 'Initial account password. May need to meet instance-specific password strength requirements.', }, recoveryKey: { type: 'string', + description: + 'DID PLC rotation key (aka, recovery key) to be included in PLC creation operation.', }, plcOp: { type: 'unknown', + description: + 'A signed DID PLC operation to be submitted as part of importing an existing account to this instance. NOTE: this optional field may be updated when full account migration is implemented.', }, }, }, @@ -2920,6 +2961,8 @@ export const schemaDict = { encoding: 'application/json', schema: { type: 'object', + description: + 'Account login session returned on successful account creation.', required: ['accessJwt', 'refreshJwt', 'handle', 'did'], properties: { accessJwt: { @@ -2935,9 +2978,11 @@ export const schemaDict = { did: { type: 'string', format: 'did', + description: 'The DID of the new account.', }, didDoc: { type: 'unknown', + description: 'Complete DID document.', }, }, }, @@ -2983,6 +3028,8 @@ export const schemaDict = { properties: { name: { type: 'string', + description: + 'A short name for the App Password, to help distinguish them.', }, }, }, @@ -3250,7 +3297,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: "Delete an actor's account with a token and password.", + description: + "Delete an actor's account with a token and password. Can only be called after requesting a deletion token. Requires auth.", input: { encoding: 'application/json', schema: { @@ -3287,7 +3335,7 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Delete the current session.', + description: 'Delete the current session. Requires auth.', }, }, }, @@ -3298,7 +3346,7 @@ export const schemaDict = { main: { type: 'query', description: - "Get a document describing the service's accounts configuration.", + "Describes the server's account creation requirements and capabilities. Implemented by PDS.", output: { encoding: 'application/json', schema: { @@ -3307,18 +3355,25 @@ export const schemaDict = { properties: { inviteCodeRequired: { type: 'boolean', + description: + 'If true, an invite code must be supplied to create an account on this instance.', }, phoneVerificationRequired: { type: 'boolean', + description: + 'If true, a phone verification token must be supplied to create an account on this instance.', }, availableUserDomains: { type: 'array', + description: + 'List of domain suffixes that can be used in account handles.', items: { type: 'string', }, }, links: { type: 'ref', + description: 'URLs of service policy documents.', ref: 'lex:com.atproto.server.describeServer#links', }, }, @@ -3344,7 +3399,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get all invite codes for a given account.', + description: + 'Get all invite codes for the current account. Requires auth.', parameters: { type: 'params', properties: { @@ -3355,6 +3411,8 @@ export const schemaDict = { createAvailable: { type: 'boolean', default: true, + description: + "Controls whether any new 'earned' but not 'created' invites should be created.", }, }, }, @@ -3388,7 +3446,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get information about the current session.', + description: + 'Get information about the current auth session. Requires auth.', output: { encoding: 'application/json', schema: { @@ -3468,7 +3527,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Refresh an authentication session.', + description: + "Refresh an authentication session. Requires auth using the 'refreshJwt' (not the 'accessJwt').", output: { encoding: 'application/json', schema: { @@ -3574,7 +3634,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Reserve a repo signing key for account creation.', + description: + 'Reserve a repo signing key, for use with account creation. Necessary so that a DID PLC update operation can be constructed during an account migraiton. Public and does not require auth; implemented by PDS. NOTE: this endpoint may change when full account migration is implemented.', input: { encoding: 'application/json', schema: { @@ -3582,7 +3643,7 @@ export const schemaDict = { properties: { did: { type: 'string', - description: 'The did to reserve a new did:key for', + description: 'The DID to reserve a key for.', }, }, }, @@ -3595,7 +3656,8 @@ export const schemaDict = { properties: { signingKey: { type: 'string', - description: 'Public signing key in the form of a did:key.', + description: + 'The public key for the reserved signing key, in did:key serialization.', }, }, }, @@ -3702,7 +3764,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a blob associated with a given repo.', + description: + 'Get a blob associated with a given account. Returns the full blob as originally uploaded. Does not require auth; implemented by PDS.', parameters: { type: 'params', required: ['did', 'cid'], @@ -3710,7 +3773,7 @@ export const schemaDict = { did: { type: 'string', format: 'did', - description: 'The DID of the repo.', + description: 'The DID of the account.', }, cid: { type: 'string', @@ -3731,7 +3794,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get blocks from a given repo.', + description: + 'Get data blocks from a given repo, by CID. For example, intermediate MST nodes, or records. Does not require auth; implemented by PDS.', parameters: { type: 'params', required: ['did', 'cids'], @@ -3826,7 +3890,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get the current commit CID & revision of the repo.', + description: + 'Get the current commit CID & revision of the specified repo. Does not require auth.', parameters: { type: 'params', required: ['did'], @@ -3869,7 +3934,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Get blocks needed for existence or non-existence of record.', + 'Get data blocks needed to prove the existence or non-existence of record in the current version of repo. Does not require auth.', parameters: { type: 'params', required: ['did', 'collection', 'rkey'], @@ -3885,6 +3950,7 @@ export const schemaDict = { }, rkey: { type: 'string', + description: 'Record Key', }, commit: { type: 'string', @@ -3906,7 +3972,7 @@ export const schemaDict = { main: { type: 'query', description: - "Gets the DID's repo, optionally catching up from a specific revision.", + "Download a repository export as CAR file. Optionally only a 'diff' since a previous revision. Does not require auth; implemented by PDS.", parameters: { type: 'params', required: ['did'], @@ -3918,7 +3984,8 @@ export const schemaDict = { }, since: { type: 'string', - description: 'The revision of the repo to catch up from.', + description: + "The revision ('rev') of the repo to create a diff from.", }, }, }, @@ -3934,7 +4001,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'List blob CIDs since some revision.', + description: + 'List blob CIDso for an account, since some repo revision. Does not require auth; implemented by PDS.', parameters: { type: 'params', required: ['did'], @@ -3987,7 +4055,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'List DIDs and root CIDs of hosted repos.', + description: + 'Enumerates all the DID, rev, and commit CID for all repos hosted by this service. Does not require auth; implemented by PDS and Relay.', parameters: { type: 'params', properties: { @@ -4033,6 +4102,7 @@ export const schemaDict = { head: { type: 'string', format: 'cid', + description: 'Current repo commit CID', }, rev: { type: 'string', @@ -4048,7 +4118,7 @@ export const schemaDict = { main: { type: 'procedure', description: - 'Notify a crawling service of a recent update; often when a long break between updates causes the connection with the crawling service to break.', + 'Notify a crawling service of a recent update, and that crawling should resume. Intended use is after a gap between repo stream events caused the crawling service to disconnect. Does not require auth; implemented by Relay.', input: { encoding: 'application/json', schema: { @@ -4058,7 +4128,7 @@ export const schemaDict = { hostname: { type: 'string', description: - 'Hostname of the service that is notifying of update.', + 'Hostname of the current service (usually a PDS) that is notifying of update.', }, }, }, @@ -4072,7 +4142,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Request a service to persistently crawl hosted repos.', + description: + 'Request a service to persistently crawl hosted repos. Expected use is new PDS instances declaring their existence to Relays. Does not require auth.', input: { encoding: 'application/json', schema: { @@ -4082,7 +4153,7 @@ export const schemaDict = { hostname: { type: 'string', description: - 'Hostname of the service that is requesting to be crawled.', + 'Hostname of the current service (eg, PDS) that is requesting to be crawled.', }, }, }, @@ -4096,13 +4167,14 @@ export const schemaDict = { defs: { main: { type: 'subscription', - description: 'Subscribe to repo updates.', + description: + 'Repository event stream, aka Firehose endpoint. Outputs repo commits with diff data, and identity update events, for all repositories on the current server. See the atproto specifications for details around stream sequencing, repo versioning, CAR diff format, and more. Public and does not require auth; implemented by PDS and Relay.', parameters: { type: 'params', properties: { cursor: { type: 'integer', - description: 'The last known event to backfill from.', + description: 'The last known event seq number to backfill from.', }, }, }, @@ -4124,11 +4196,15 @@ export const schemaDict = { }, { name: 'ConsumerTooSlow', + description: + 'If the consumer of the stream can not keep up with events, and a backlog gets too large, the server will drop the connection.', }, ], }, commit: { type: 'object', + description: + 'Represents an update of repository state. Note that empty commits are allowed, which include no repo data changes, but an update to rev and signature.', required: [ 'seq', 'rebase', @@ -4146,34 +4222,45 @@ export const schemaDict = { properties: { seq: { type: 'integer', + description: 'The stream sequence number of this message.', }, rebase: { type: 'boolean', + description: 'DEPRECATED -- unused', }, tooBig: { type: 'boolean', + description: + 'Indicates that this commit contained too many ops, or data size was too large. Consumers will need to make a separate request to get missing data.', }, repo: { type: 'string', format: 'did', + description: 'The repo this event comes from.', }, commit: { type: 'cid-link', + description: 'Repo commit object CID.', }, prev: { type: 'cid-link', + description: + 'DEPRECATED -- unused. WARNING -- nullable and optional; stick with optional to ensure golang interoperability.', }, rev: { type: 'string', - description: 'The rev of the emitted commit.', + description: + 'The rev of the emitted commit. Note that this information is also in the commit object included in blocks, unless this is a tooBig event.', }, since: { type: 'string', - description: 'The rev of the last emitted commit from this repo.', + description: + 'The rev of the last emitted commit from this repo (if any).', }, blocks: { type: 'bytes', - description: 'CAR file containing relevant blocks.', + description: + 'CAR file containing relevant blocks, as a diff since the previous repo state.', maxLength: 1000000, }, ops: { @@ -4181,6 +4268,8 @@ export const schemaDict = { items: { type: 'ref', ref: 'lex:com.atproto.sync.subscribeRepos#repoOp', + description: + 'List of repo mutation operations in this commit (eg, records created, updated, or deleted).', }, maxLength: 200, }, @@ -4188,16 +4277,22 @@ export const schemaDict = { type: 'array', items: { type: 'cid-link', + description: + 'List of new blobs (by CID) referenced by records in this commit.', }, }, time: { type: 'string', format: 'datetime', + description: + 'Timestamp of when this message was originally broadcast.', }, }, }, handle: { type: 'object', + description: + "Represents an update of the account's handle, or transition to/from invalid state.", required: ['seq', 'did', 'handle', 'time'], properties: { seq: { @@ -4219,6 +4314,8 @@ export const schemaDict = { }, migrate: { type: 'object', + description: + 'Represents an account moving from one PDS instance to another. NOTE: not implemented; full account migration may introduce a new message instead.', required: ['seq', 'did', 'migrateTo', 'time'], nullable: ['migrateTo'], properties: { @@ -4240,6 +4337,7 @@ export const schemaDict = { }, tombstone: { type: 'object', + description: 'Indicates that an account has been deleted.', required: ['seq', 'did', 'time'], properties: { seq: { @@ -4270,8 +4368,7 @@ export const schemaDict = { }, repoOp: { type: 'object', - description: - "A repo operation, ie a write of a single record. For creates and updates, CID is the record's CID as of this operation. For deletes, it's null.", + description: 'A repo operation, ie a mutation of a single record.', required: ['action', 'path', 'cid'], nullable: ['cid'], properties: { @@ -4284,6 +4381,8 @@ export const schemaDict = { }, cid: { type: 'cid-link', + description: + 'For creates and updates, the new record CID. For deletions, null.', }, }, }, @@ -4324,7 +4423,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Fetch all labels from a labeler created after a certain date.', + 'Fetch all labels from a labeler created after a certain date. DEPRECATED: use queryLabels or subscribeLabels instead', parameters: { type: 'params', properties: { @@ -4440,7 +4539,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Transfer an account.', + description: + 'Transfer an account. NOTE: temporary method, necessarily how account migration will be implemented.', input: { encoding: 'application/json', schema: { @@ -4513,7 +4613,6 @@ export const schemaDict = { AppBskyActorDefs: { lexicon: 1, id: 'app.bsky.actor.defs', - description: 'A reference to an actor in the network.', defs: { profileViewBasic: { type: 'object', @@ -4646,6 +4745,8 @@ export const schemaDict = { }, viewerState: { type: 'object', + description: + "Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests.", properties: { muted: { type: 'boolean', @@ -4815,7 +4916,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get private preferences attached to the account.', + description: + 'Get private preferences attached to the current account. Expected use is synchronization between multiple devices, and import/export during account migration. Requires auth.', parameters: { type: 'params', properties: {}, @@ -4842,7 +4944,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get detailed profile view of an actor.', + description: + 'Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth.', parameters: { type: 'params', required: ['actor'], @@ -4850,6 +4953,7 @@ export const schemaDict = { actor: { type: 'string', format: 'at-identifier', + description: 'Handle or DID of account to fetch profile of.', }, }, }, @@ -4909,7 +5013,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of suggested actors, used for discovery.', + description: + 'Get a list of suggested actors. Expected use is discovery of accounts to follow during new account onboarding.', parameters: { type: 'params', properties: { @@ -4952,7 +5057,7 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a profile.', + description: 'A declaration of a Bluesky account profile.', key: 'literal:self', record: { type: 'object', @@ -4964,21 +5069,28 @@ export const schemaDict = { }, description: { type: 'string', + description: 'Free-form profile description text.', maxGraphemes: 256, maxLength: 2560, }, avatar: { type: 'blob', + description: + "Small image to be displayed next to posts from account. AKA, 'profile picture'", accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, banner: { type: 'blob', + description: + 'Larger horizontal image to display behind profile view.', accept: ['image/png', 'image/jpeg'], maxSize: 1000000, }, labels: { type: 'union', + description: + 'Self-label values, specific to the Bluesky application, on the overall account.', refs: ['lex:com.atproto.label.defs#selfLabels'], }, }, @@ -5015,7 +5127,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Find actors (profiles) matching search criteria.', + description: + 'Find actors (profiles) matching search criteria. Does not require auth.', parameters: { type: 'params', properties: { @@ -5067,7 +5180,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Find actor suggestions for a prefix search term.', + description: + 'Find actor suggestions for a prefix search term. Expected use is for auto-completion during text field entry. Does not require auth.', parameters: { type: 'params', properties: { @@ -5109,11 +5223,11 @@ export const schemaDict = { AppBskyEmbedExternal: { lexicon: 1, id: 'app.bsky.embed.external', - description: - 'A representation of some externally linked content, embedded in another form of content.', defs: { main: { type: 'object', + description: + "A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post).", required: ['external'], properties: { external: { @@ -5177,7 +5291,7 @@ export const schemaDict = { AppBskyEmbedImages: { lexicon: 1, id: 'app.bsky.embed.images', - description: 'A set of images embedded in some other form of content.', + description: 'A set of images embedded in a Bluesky record (eg, a post).', defs: { main: { type: 'object', @@ -5204,6 +5318,8 @@ export const schemaDict = { }, alt: { type: 'string', + description: + 'Alt text description of the image, for accessibility.', }, aspectRatio: { type: 'ref', @@ -5247,12 +5363,18 @@ export const schemaDict = { properties: { thumb: { type: 'string', + description: + 'Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View.', }, fullsize: { type: 'string', + description: + 'Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View.', }, alt: { type: 'string', + description: + 'Alt text description of the image, for accessibility.', }, aspectRatio: { type: 'ref', @@ -5266,7 +5388,7 @@ export const schemaDict = { lexicon: 1, id: 'app.bsky.embed.record', description: - 'A representation of a record embedded in another form of content.', + 'A representation of a record embedded in a Bluesky record (eg, a post). For example, a quote-post, or sharing a feed generator record.', defs: { main: { type: 'object', @@ -5312,6 +5434,7 @@ export const schemaDict = { }, value: { type: 'unknown', + description: 'The record data itself.', }, labels: { type: 'array', @@ -5376,7 +5499,7 @@ export const schemaDict = { lexicon: 1, id: 'app.bsky.embed.recordWithMedia', description: - 'A representation of a record embedded in another form of content, alongside other compatible embeds.', + 'A representation of a record embedded in a Bluesky record (eg, a post), alongside other compatible embeds. For example, a quote post and image, or a quote post and external URL card.', defs: { main: { type: 'object', @@ -5475,6 +5598,8 @@ export const schemaDict = { }, viewerState: { type: 'object', + description: + "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests.", properties: { repost: { type: 'string', @@ -5735,7 +5860,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Get information about a feed generator, including policies and offered feed URIs.', + 'Get information about a feed generator, including policies and offered feed URIs. Does not require auth; implemented by Feed Generator services (not App View).', output: { encoding: 'application/json', schema: { @@ -5790,7 +5915,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of the existence of a feed generator.', + description: + 'Record declaring of the existence of a feed generator, and containing metadata about it. The record can exist in any repository.', key: 'any', record: { type: 'object', @@ -5824,6 +5950,7 @@ export const schemaDict = { }, labels: { type: 'union', + description: 'Self-label values', refs: ['lex:com.atproto.label.defs#selfLabels'], }, createdAt: { @@ -5841,7 +5968,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of feeds created by the actor.', + description: + "Get a list of feeds (feed generator records) created by the actor (in the actor's repo).", parameters: { type: 'params', required: ['actor'], @@ -5889,7 +6017,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of posts liked by an actor.', + description: + 'Get a list of posts liked by an actor. Does not require auth.', parameters: { type: 'params', required: ['actor'], @@ -5945,7 +6074,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: "Get a view of an actor's feed.", + description: + "Get a view of an actor's 'author feed' (post and reposts by the author). Does not require auth.", parameters: { type: 'params', required: ['actor'], @@ -5965,6 +6095,8 @@ export const schemaDict = { }, filter: { type: 'string', + description: + 'Combinations of post/repost types to include in response.', knownValues: [ 'posts_with_replies', 'posts_no_replies', @@ -6012,7 +6144,7 @@ export const schemaDict = { main: { type: 'query', description: - "Get a hydrated feed from an actor's selected feed generator.", + "Get a hydrated feed from an actor's selected feed generator. Implemented by App View.", parameters: { type: 'params', required: ['feed'], @@ -6065,7 +6197,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get information about a feed generator.', + description: + 'Get information about a feed generator. Implemented by AppView.', parameters: { type: 'params', required: ['feed'], @@ -6073,6 +6206,7 @@ export const schemaDict = { feed: { type: 'string', format: 'at-uri', + description: 'AT-URI of the feed generator record.', }, }, }, @@ -6088,9 +6222,13 @@ export const schemaDict = { }, isOnline: { type: 'boolean', + description: + 'Indicates whether the feed generator service has been online recently, or else seems to be inactive.', }, isValid: { type: 'boolean', + description: + 'Indicates whether the feed generator service is compatible with the record declaration.', }, }, }, @@ -6143,7 +6281,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a skeleton of a feed provided by a feed generator.', + description: + 'Get a skeleton of a feed provided by a feed generator. Auth is optional, depending on provider requirements, and provides the DID of the requester. Implemented by Feed Generator Service.', parameters: { type: 'params', required: ['feed'], @@ -6151,6 +6290,8 @@ export const schemaDict = { feed: { type: 'string', format: 'at-uri', + description: + 'Reference to feed generator record describing the specific feed being requested.', }, limit: { type: 'integer', @@ -6196,7 +6337,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get the list of likes.', + description: + 'Get like records which reference a subject (by AT-URI and CID).', parameters: { type: 'params', required: ['uri'], @@ -6204,10 +6346,13 @@ export const schemaDict = { uri: { type: 'string', format: 'at-uri', + description: 'AT-URI of the subject (eg, a post record).', }, cid: { type: 'string', format: 'cid', + description: + 'CID of the subject record (aka, specific version of record), to filter likes.', }, limit: { type: 'integer', @@ -6274,7 +6419,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a view of a recent posts from actors in a list.', + description: + 'Get a feed of recent posts from a list (posts and reposts from any actors on the list). Does not require auth.', parameters: { type: 'params', required: ['list'], @@ -6282,6 +6428,7 @@ export const schemaDict = { list: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) to the list record.', }, limit: { type: 'integer', @@ -6327,7 +6474,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get posts in a thread.', + description: + 'Get posts in a thread. Does not require auth, but additional metadata and filtering will be applied for authed requests.', parameters: { type: 'params', required: ['uri'], @@ -6335,15 +6483,20 @@ export const schemaDict = { uri: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) to post record.', }, depth: { type: 'integer', + description: + 'How many levels of reply depth should be included in response.', default: 6, minimum: 0, maximum: 1000, }, parentHeight: { type: 'integer', + description: + 'How many levels of parent (and grandparent, etc) post to include.', default: 80, minimum: 0, maximum: 1000, @@ -6381,13 +6534,15 @@ export const schemaDict = { defs: { main: { type: 'query', - description: "Get a view of an actor's feed.", + description: + "Gets post views for a specified list of posts (by AT-URI). This is sometimes referred to as 'hydrating' a 'feed skeleton'.", parameters: { type: 'params', required: ['uris'], properties: { uris: { type: 'array', + description: 'List of post AT-URIs to return hydrated views for.', items: { type: 'string', format: 'at-uri', @@ -6421,7 +6576,7 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of reposts.', + description: 'Get a list of reposts for a given post.', parameters: { type: 'params', required: ['uri'], @@ -6429,10 +6584,13 @@ export const schemaDict = { uri: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) of post record', }, cid: { type: 'string', format: 'cid', + description: + 'If supplied, filters to reposts of specific version (by CID) of the post record.', }, limit: { type: 'integer', @@ -6481,7 +6639,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of suggested feeds for the viewer.', + description: + 'Get a list of suggested feeds (feed generators) for the requesting account.', parameters: { type: 'params', properties: { @@ -6524,12 +6683,15 @@ export const schemaDict = { defs: { main: { type: 'query', - description: "Get a view of the actor's home timeline.", + description: + "Get a view of the requesting account's home timeline. This is expected to be some form of reverse-chronological feed.", parameters: { type: 'params', properties: { algorithm: { type: 'string', + description: + "Variant 'algorithm' for timeline. Implementation-specific. NOTE: most feed flexibility has been moved to feed generator mechanism.", }, limit: { type: 'integer', @@ -6570,7 +6732,7 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a like.', + description: "Record declaring a 'like' of a piece of subject content.", key: 'tid', record: { type: 'object', @@ -6595,7 +6757,7 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a post.', + description: 'Record containing a Bluesky post.', key: 'tid', record: { type: 'object', @@ -6605,10 +6767,12 @@ export const schemaDict = { type: 'string', maxLength: 3000, maxGraphemes: 300, + description: + 'The primary post content. May be an empty string, if there are embeds.', }, entities: { type: 'array', - description: 'Deprecated: replaced by app.bsky.richtext.facet.', + description: 'DEPRECATED: replaced by app.bsky.richtext.facet.', items: { type: 'ref', ref: 'lex:app.bsky.feed.post#entity', @@ -6616,6 +6780,8 @@ export const schemaDict = { }, facets: { type: 'array', + description: + 'Annotations of text (mentions, URLs, hashtags, etc)', items: { type: 'ref', ref: 'lex:app.bsky.richtext.facet', @@ -6636,6 +6802,8 @@ export const schemaDict = { }, langs: { type: 'array', + description: + 'Indicates human language of post primary text content.', maxLength: 3, items: { type: 'string', @@ -6644,21 +6812,25 @@ export const schemaDict = { }, labels: { type: 'union', + description: + 'Self-label values for this post. Effectively content warnings.', refs: ['lex:com.atproto.label.defs#selfLabels'], }, tags: { type: 'array', + description: 'Additional non-inline tags describing this post.', maxLength: 8, items: { type: 'string', maxLength: 640, maxGraphemes: 64, }, - description: 'Additional non-inline tags describing this post.', }, createdAt: { type: 'string', format: 'datetime', + description: + 'Client-declared timestamp when this post was originally created.', }, }, }, @@ -6718,7 +6890,8 @@ export const schemaDict = { id: 'app.bsky.feed.repost', defs: { main: { - description: 'A declaration of a repost.', + description: + "Record representing a 'repost' of an existing Bluesky post.", type: 'record', key: 'tid', record: { @@ -6744,7 +6917,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Find posts matching search criteria.', + description: + 'Find posts matching search criteria, returning views of those posts.', parameters: { type: 'params', required: ['q'], @@ -6807,7 +6981,7 @@ export const schemaDict = { type: 'record', key: 'tid', description: - "Defines interaction gating rules for a thread. The rkey of the threadgate record should match the rkey of the thread's root post.", + "Record defining interaction gating rules for a thread (aka, reply controls). The record key (rkey) of the threadgate record must match the record key of the thread's root post, and that record must be in the same repository..", record: { type: 'object', required: ['post', 'createdAt'], @@ -6815,6 +6989,7 @@ export const schemaDict = { post: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) to the post record.', }, allow: { type: 'array', @@ -6864,7 +7039,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a block.', + description: + "Record declaring a 'block' relationship against another account. NOTE: blocks are public in Bluesky; see blog posts for details.", key: 'tid', record: { type: 'object', @@ -6873,6 +7049,7 @@ export const schemaDict = { subject: { type: 'string', format: 'did', + description: 'DID of the account to be blocked.', }, createdAt: { type: 'string', @@ -7061,7 +7238,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a social follow.', + description: + "Record declaring a social 'follow' relationship of another account. Duplicate follows will be ignored by the AppView.", key: 'tid', record: { type: 'object', @@ -7086,7 +7264,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of who the actor is blocking.', + description: + 'Enumerates which accounts the requesting account is currently blocking. Requires auth.', parameters: { type: 'params', properties: { @@ -7129,7 +7308,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: "Get a list of an actor's followers.", + description: + 'Enumerates accounts which follow a specified account (actor).', parameters: { type: 'params', required: ['actor'], @@ -7181,7 +7361,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of who the actor follows.', + description: + 'Enumerates accounts which a specified account (actor) follows.', parameters: { type: 'params', required: ['actor'], @@ -7233,7 +7414,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of actors.', + description: + "Gets a 'view' (with additional context) of a specified list.", parameters: { type: 'params', required: ['list'], @@ -7241,6 +7423,7 @@ export const schemaDict = { list: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) of the list record to hydrate.', }, limit: { type: 'integer', @@ -7285,7 +7468,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get lists that the actor is blocking.', + description: + 'Get mod lists that the requesting account (actor) is blocking. Requires auth.', parameters: { type: 'params', properties: { @@ -7328,7 +7512,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get lists that the actor is muting.', + description: + 'Enumerates mod lists that the requesting account (actor) currently has muted. Requires auth.', parameters: { type: 'params', properties: { @@ -7371,7 +7556,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of lists that belong to an actor.', + description: + 'Enumerates the lists created by a specified account (actor).', parameters: { type: 'params', required: ['actor'], @@ -7379,6 +7565,7 @@ export const schemaDict = { actor: { type: 'string', format: 'at-identifier', + description: 'The account (actor) to enumerate lists from.', }, limit: { type: 'integer', @@ -7419,7 +7606,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of who the actor mutes.', + description: + 'Enumerates accounts that the requesting account (actor) currently has muted. Requires auth.', parameters: { type: 'params', properties: { @@ -7463,7 +7651,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Enumerates public relationships between one account, and a list of other accounts', + 'Enumerates public relationships between one account, and a list of other accounts. Does not require auth.', parameters: { type: 'params', required: ['actor'], @@ -7471,9 +7659,12 @@ export const schemaDict = { actor: { type: 'string', format: 'at-identifier', + description: 'Primary account requesting relationships for.', }, others: { type: 'array', + description: + "List of 'other' accounts to be related back to the primary.", maxLength: 30, items: { type: 'string', @@ -7521,7 +7712,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get suggested follows related to a given actor.', + description: + 'Enumerates follows similar to a given account (actor). Expected use is to recommend additional accounts immediately after following one account.', parameters: { type: 'params', required: ['actor'], @@ -7557,7 +7749,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A declaration of a list of actors.', + description: + 'Record representing a list of accounts (actors). Scope includes both moderation-oriented lists and curration-oriented lists.', key: 'tid', record: { type: 'object', @@ -7565,12 +7758,15 @@ export const schemaDict = { properties: { purpose: { type: 'ref', + description: + 'Defines the purpose of the list (aka, moderation-oriented or curration-oriented)', ref: 'lex:app.bsky.graph.defs#listPurpose', }, name: { type: 'string', maxLength: 64, minLength: 1, + description: 'Display name for list; can not be empty.', }, description: { type: 'string', @@ -7608,7 +7804,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'A block of an entire list of actors.', + description: + 'Record representing a block relationship against an entire an entire list of accounts (actors).', key: 'tid', record: { type: 'object', @@ -7617,6 +7814,7 @@ export const schemaDict = { subject: { type: 'string', format: 'at-uri', + description: 'Reference (AT-URI) to the mod list record.', }, createdAt: { type: 'string', @@ -7633,7 +7831,8 @@ export const schemaDict = { defs: { main: { type: 'record', - description: 'An item under a declared list of actors.', + description: + "Record representing an account's inclusion on a specific list. The AppView will ignore duplicate listitem records.", key: 'tid', record: { type: 'object', @@ -7642,10 +7841,13 @@ export const schemaDict = { subject: { type: 'string', format: 'did', + description: 'The account which is included on the list.', }, list: { type: 'string', format: 'at-uri', + description: + 'Reference (AT-URI) to the list record (app.bsky.graph.list).', }, createdAt: { type: 'string', @@ -7662,7 +7864,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Mute an actor by DID or handle.', + description: + 'Creates a mute relationship for the specified account. Mutes are private in Bluesky. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7685,7 +7888,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Mute a list of actors.', + description: + 'Creates a mute relationship for the specified list of accounts. Mutes are private in Bluesky. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7708,7 +7912,7 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Unmute an actor by DID or handle.', + description: 'Unmutes the specified account. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7731,7 +7935,7 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Unmute a list of actors.', + description: 'Unmutes the specified list of accounts. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7754,7 +7958,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get the count of unread notifications.', + description: + 'Count the number of unread notifications for the requesting account. Requires auth.', parameters: { type: 'params', properties: { @@ -7785,7 +7990,8 @@ export const schemaDict = { defs: { main: { type: 'query', - description: 'Get a list of notifications.', + description: + 'Enumerate notifications for the requesting account. Requires auth.', parameters: { type: 'params', properties: { @@ -7896,7 +8102,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Register for push notifications with a service.', + description: + 'Register to receive push notifications, via a specified service, for the requesting account. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7929,7 +8136,8 @@ export const schemaDict = { defs: { main: { type: 'procedure', - description: 'Notify server that the user has seen notifications.', + description: + 'Notify server that the requesting account has seen notifications. Requires auth.', input: { encoding: 'application/json', schema: { @@ -7952,6 +8160,7 @@ export const schemaDict = { defs: { main: { type: 'object', + description: 'Annotation of a sub-string within rich text.', required: ['index', 'features'], properties: { index: { @@ -7973,7 +8182,8 @@ export const schemaDict = { }, mention: { type: 'object', - description: 'A facet feature for actor mentions.', + description: + "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID.", required: ['did'], properties: { did: { @@ -7984,7 +8194,8 @@ export const schemaDict = { }, link: { type: 'object', - description: 'A facet feature for links.', + description: + 'Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL.', required: ['uri'], properties: { uri: { @@ -7995,7 +8206,8 @@ export const schemaDict = { }, tag: { type: 'object', - description: 'A hashtag.', + description: + "Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags').", required: ['tag'], properties: { tag: { @@ -8008,7 +8220,7 @@ export const schemaDict = { byteSlice: { type: 'object', description: - 'A text segment. Start is inclusive, end is exclusive. Indices are for utf8-encoded strings.', + 'Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets.', required: ['byteStart', 'byteEnd'], properties: { byteStart: { diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts b/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts index 8cdcafcb72f..ff49c40dc0b 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts @@ -82,6 +82,7 @@ export function validateProfileViewDetailed(v: unknown): ValidationResult { return lexicons.validate('app.bsky.actor.defs#profileViewDetailed', v) } +/** Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests. */ export interface ViewerState { muted?: boolean mutedByList?: AppBskyGraphDefs.ListViewBasic diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/getProfile.ts b/packages/pds/src/lexicon/types/app/bsky/actor/getProfile.ts index be58c73a233..5a7b1f25bfc 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/getProfile.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/getProfile.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from './defs' export interface QueryParams { + /** Handle or DID of account to fetch profile of. */ actor: string } diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/profile.ts b/packages/pds/src/lexicon/types/app/bsky/actor/profile.ts index 7dbc4c1ccec..8810ce7bed9 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/profile.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/profile.ts @@ -9,8 +9,11 @@ import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface Record { displayName?: string + /** Free-form profile description text. */ description?: string + /** Small image to be displayed next to posts from account. AKA, 'profile picture' */ avatar?: BlobRef + /** Larger horizontal image to display behind profile view. */ banner?: BlobRef labels?: | ComAtprotoLabelDefs.SelfLabels diff --git a/packages/pds/src/lexicon/types/app/bsky/embed/external.ts b/packages/pds/src/lexicon/types/app/bsky/embed/external.ts index f42a6cfd95c..b137ee4b6f5 100644 --- a/packages/pds/src/lexicon/types/app/bsky/embed/external.ts +++ b/packages/pds/src/lexicon/types/app/bsky/embed/external.ts @@ -6,6 +6,7 @@ import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' +/** A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post). */ export interface Main { external: External [k: string]: unknown diff --git a/packages/pds/src/lexicon/types/app/bsky/embed/images.ts b/packages/pds/src/lexicon/types/app/bsky/embed/images.ts index 4864fad3dea..96399867a1a 100644 --- a/packages/pds/src/lexicon/types/app/bsky/embed/images.ts +++ b/packages/pds/src/lexicon/types/app/bsky/embed/images.ts @@ -26,6 +26,7 @@ export function validateMain(v: unknown): ValidationResult { export interface Image { image: BlobRef + /** Alt text description of the image, for accessibility. */ alt: string aspectRatio?: AspectRatio [k: string]: unknown @@ -76,8 +77,11 @@ export function validateView(v: unknown): ValidationResult { } export interface ViewImage { + /** Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View. */ thumb: string + /** Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View. */ fullsize: string + /** Alt text description of the image, for accessibility. */ alt: string aspectRatio?: AspectRatio [k: string]: unknown diff --git a/packages/pds/src/lexicon/types/app/bsky/embed/record.ts b/packages/pds/src/lexicon/types/app/bsky/embed/record.ts index cea5742a45e..dbe7f13152b 100644 --- a/packages/pds/src/lexicon/types/app/bsky/embed/record.ts +++ b/packages/pds/src/lexicon/types/app/bsky/embed/record.ts @@ -57,6 +57,7 @@ export interface ViewRecord { uri: string cid: string author: AppBskyActorDefs.ProfileViewBasic + /** The record data itself. */ value: {} labels?: ComAtprotoLabelDefs.Label[] embeds?: ( diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/defs.ts b/packages/pds/src/lexicon/types/app/bsky/feed/defs.ts index 382d3f58ecf..261d8a622ec 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/defs.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/defs.ts @@ -45,6 +45,7 @@ export function validatePostView(v: unknown): ValidationResult { return lexicons.validate('app.bsky.feed.defs#postView', v) } +/** Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests. */ export interface ViewerState { repost?: string like?: string diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts index 8f8e038c71f..017c7a6a2d4 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts @@ -13,6 +13,7 @@ export interface QueryParams { actor: string limit: number cursor?: string + /** Combinations of post/repost types to include in response. */ filter: | 'posts_with_replies' | 'posts_no_replies' diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts index b65a5151d46..7ab89057a8c 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getFeedGenerator.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** AT-URI of the feed generator record. */ feed: string } @@ -17,7 +18,9 @@ export type InputSchema = undefined export interface OutputSchema { view: AppBskyFeedDefs.GeneratorView + /** Indicates whether the feed generator service has been online recently, or else seems to be inactive. */ isOnline: boolean + /** Indicates whether the feed generator service is compatible with the record declaration. */ isValid: boolean [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts index ab1911ecb87..ca1cef20f08 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getFeedSkeleton.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** Reference to feed generator record describing the specific feed being requested. */ feed: string limit: number cursor?: string diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getLikes.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getLikes.ts index c7e8c860bd6..275d99bba3d 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getLikes.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getLikes.ts @@ -10,7 +10,9 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { + /** AT-URI of the subject (eg, a post record). */ uri: string + /** CID of the subject record (aka, specific version of record), to filter likes. */ cid?: string limit: number cursor?: string diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getListFeed.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getListFeed.ts index ab157788f72..84e12deaa92 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getListFeed.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getListFeed.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** Reference (AT-URI) to the list record. */ list: string limit: number cursor?: string diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getPostThread.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getPostThread.ts index 9e81da0eff6..ae232fd91a2 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getPostThread.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getPostThread.ts @@ -10,8 +10,11 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** Reference (AT-URI) to post record. */ uri: string + /** How many levels of reply depth should be included in response. */ depth: number + /** How many levels of parent (and grandparent, etc) post to include. */ parentHeight: number } diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getPosts.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getPosts.ts index 439e2132201..85000c74787 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getPosts.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getPosts.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** List of post AT-URIs to return hydrated views for. */ uris: string[] } diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getRepostedBy.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getRepostedBy.ts index 6dea2b753c7..40e008815d9 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getRepostedBy.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getRepostedBy.ts @@ -10,7 +10,9 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyActorDefs from '../actor/defs' export interface QueryParams { + /** Reference (AT-URI) of post record */ uri: string + /** If supplied, filters to reposts of specific version (by CID) of the post record. */ cid?: string limit: number cursor?: string diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/getTimeline.ts b/packages/pds/src/lexicon/types/app/bsky/feed/getTimeline.ts index f37def5808e..5202c9eb6e3 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/getTimeline.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/getTimeline.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyFeedDefs from './defs' export interface QueryParams { + /** Variant 'algorithm' for timeline. Implementation-specific. NOTE: most feed flexibility has been moved to feed generator mechanism. */ algorithm?: string limit: number cursor?: string diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/post.ts b/packages/pds/src/lexicon/types/app/bsky/feed/post.ts index 93870b4452d..c30825e118a 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/post.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/post.ts @@ -14,9 +14,11 @@ import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' export interface Record { + /** The primary post content. May be an empty string, if there are embeds. */ text: string - /** Deprecated: replaced by app.bsky.richtext.facet. */ + /** DEPRECATED: replaced by app.bsky.richtext.facet. */ entities?: Entity[] + /** Annotations of text (mentions, URLs, hashtags, etc) */ facets?: AppBskyRichtextFacet.Main[] reply?: ReplyRef embed?: @@ -25,12 +27,14 @@ export interface Record { | AppBskyEmbedRecord.Main | AppBskyEmbedRecordWithMedia.Main | { $type: string; [k: string]: unknown } + /** Indicates human language of post primary text content. */ langs?: string[] labels?: | ComAtprotoLabelDefs.SelfLabels | { $type: string; [k: string]: unknown } /** Additional non-inline tags describing this post. */ tags?: string[] + /** Client-declared timestamp when this post was originally created. */ createdAt: string [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/threadgate.ts b/packages/pds/src/lexicon/types/app/bsky/feed/threadgate.ts index 6a190d6e98a..e14140d5609 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/threadgate.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/threadgate.ts @@ -7,6 +7,7 @@ import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' export interface Record { + /** Reference (AT-URI) to the post record. */ post: string allow?: ( | MentionRule diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/block.ts b/packages/pds/src/lexicon/types/app/bsky/graph/block.ts index 947463af422..b7f19f126b7 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/block.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/block.ts @@ -7,6 +7,7 @@ import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' export interface Record { + /** DID of the account to be blocked. */ subject: string createdAt: string [k: string]: unknown diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getList.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getList.ts index 6b30cd7faa9..864a81b3833 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getList.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getList.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { + /** Reference (AT-URI) of the list record to hydrate. */ list: string limit: number cursor?: string diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getLists.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getLists.ts index 6bcb3134a47..dc0c4f18bea 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getLists.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getLists.ts @@ -10,6 +10,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { + /** The account (actor) to enumerate lists from. */ actor: string limit: number cursor?: string diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/getRelationships.ts b/packages/pds/src/lexicon/types/app/bsky/graph/getRelationships.ts index 0125414ccd4..bd6b6e765ed 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/getRelationships.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/getRelationships.ts @@ -10,7 +10,9 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' import * as AppBskyGraphDefs from './defs' export interface QueryParams { + /** Primary account requesting relationships for. */ actor: string + /** List of 'other' accounts to be related back to the primary. */ others?: string[] } diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/list.ts b/packages/pds/src/lexicon/types/app/bsky/graph/list.ts index 36a7fb17a3f..91c8ccee5bb 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/list.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/list.ts @@ -11,6 +11,7 @@ import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' export interface Record { purpose: AppBskyGraphDefs.ListPurpose + /** Display name for list; can not be empty. */ name: string description?: string descriptionFacets?: AppBskyRichtextFacet.Main[] diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/listblock.ts b/packages/pds/src/lexicon/types/app/bsky/graph/listblock.ts index 59f2e057eb5..592778c7a51 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/listblock.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/listblock.ts @@ -7,6 +7,7 @@ import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' export interface Record { + /** Reference (AT-URI) to the mod list record. */ subject: string createdAt: string [k: string]: unknown diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/listitem.ts b/packages/pds/src/lexicon/types/app/bsky/graph/listitem.ts index 69eff329ed4..5e93b34a111 100644 --- a/packages/pds/src/lexicon/types/app/bsky/graph/listitem.ts +++ b/packages/pds/src/lexicon/types/app/bsky/graph/listitem.ts @@ -7,7 +7,9 @@ import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' export interface Record { + /** The account which is included on the list. */ subject: string + /** Reference (AT-URI) to the list record (app.bsky.graph.list). */ list: string createdAt: string [k: string]: unknown diff --git a/packages/pds/src/lexicon/types/app/bsky/richtext/facet.ts b/packages/pds/src/lexicon/types/app/bsky/richtext/facet.ts index 2c5b2d723a9..139b5382caf 100644 --- a/packages/pds/src/lexicon/types/app/bsky/richtext/facet.ts +++ b/packages/pds/src/lexicon/types/app/bsky/richtext/facet.ts @@ -6,6 +6,7 @@ import { lexicons } from '../../../../lexicons' import { isObj, hasProp } from '../../../../util' import { CID } from 'multiformats/cid' +/** Annotation of a sub-string within rich text. */ export interface Main { index: ByteSlice features: (Mention | Link | Tag | { $type: string; [k: string]: unknown })[] @@ -25,7 +26,7 @@ export function validateMain(v: unknown): ValidationResult { return lexicons.validate('app.bsky.richtext.facet#main', v) } -/** A facet feature for actor mentions. */ +/** Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID. */ export interface Mention { did: string [k: string]: unknown @@ -43,7 +44,7 @@ export function validateMention(v: unknown): ValidationResult { return lexicons.validate('app.bsky.richtext.facet#mention', v) } -/** A facet feature for links. */ +/** Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL. */ export interface Link { uri: string [k: string]: unknown @@ -61,7 +62,7 @@ export function validateLink(v: unknown): ValidationResult { return lexicons.validate('app.bsky.richtext.facet#link', v) } -/** A hashtag. */ +/** Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags'). */ export interface Tag { tag: string [k: string]: unknown @@ -77,7 +78,7 @@ export function validateTag(v: unknown): ValidationResult { return lexicons.validate('app.bsky.richtext.facet#tag', v) } -/** A text segment. Start is inclusive, end is exclusive. Indices are for utf8-encoded strings. */ +/** Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets. */ export interface ByteSlice { byteStart: number byteEnd: number diff --git a/packages/pds/src/lexicon/types/com/atproto/identity/updateHandle.ts b/packages/pds/src/lexicon/types/com/atproto/identity/updateHandle.ts index 6782a68ed54..f451d1f57c7 100644 --- a/packages/pds/src/lexicon/types/com/atproto/identity/updateHandle.ts +++ b/packages/pds/src/lexicon/types/com/atproto/identity/updateHandle.ts @@ -11,6 +11,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { + /** The new handle. */ handle: string [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/com/atproto/label/subscribeLabels.ts b/packages/pds/src/lexicon/types/com/atproto/label/subscribeLabels.ts index 9d4b4441ae0..6034b35d895 100644 --- a/packages/pds/src/lexicon/types/com/atproto/label/subscribeLabels.ts +++ b/packages/pds/src/lexicon/types/com/atproto/label/subscribeLabels.ts @@ -10,7 +10,7 @@ import { IncomingMessage } from 'http' import * as ComAtprotoLabelDefs from './defs' export interface QueryParams { - /** The last known event to backfill from. */ + /** The last known event seq number to backfill from. */ cursor?: number } diff --git a/packages/pds/src/lexicon/types/com/atproto/moderation/createReport.ts b/packages/pds/src/lexicon/types/com/atproto/moderation/createReport.ts index c1335eb3d1f..aa3f810a91c 100644 --- a/packages/pds/src/lexicon/types/com/atproto/moderation/createReport.ts +++ b/packages/pds/src/lexicon/types/com/atproto/moderation/createReport.ts @@ -15,6 +15,7 @@ export interface QueryParams {} export interface InputSchema { reasonType: ComAtprotoModerationDefs.ReasonType + /** Additional context about the content and violation. */ reason?: string subject: | ComAtprotoAdminDefs.RepoRef diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/applyWrites.ts b/packages/pds/src/lexicon/types/com/atproto/repo/applyWrites.ts index 7bdb6dc2ed1..3956d7c3048 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/applyWrites.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/applyWrites.ts @@ -11,11 +11,12 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ + /** The handle or DID of the repo (aka, current account). */ repo: string - /** Flag for validating the records. */ + /** Can be set to 'false' to skip Lexicon schema validation of record data, for all operations. */ validate: boolean writes: (Create | Update | Delete)[] + /** If provided, the entire operation will fail if the current repo commit CID does not match this value. Used to prevent conflicting repo mutations. */ swapCommit?: string [k: string]: unknown } @@ -43,7 +44,7 @@ export type Handler = ( ctx: HandlerReqCtx, ) => Promise | HandlerOutput -/** Create a new record. */ +/** Operation which creates a new record. */ export interface Create { collection: string rkey?: string @@ -63,7 +64,7 @@ export function validateCreate(v: unknown): ValidationResult { return lexicons.validate('com.atproto.repo.applyWrites#create', v) } -/** Update an existing record. */ +/** Operation which updates an existing record. */ export interface Update { collection: string rkey: string @@ -83,7 +84,7 @@ export function validateUpdate(v: unknown): ValidationResult { return lexicons.validate('com.atproto.repo.applyWrites#update', v) } -/** Delete an existing record. */ +/** Operation which deletes an existing record. */ export interface Delete { collection: string rkey: string diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/createRecord.ts b/packages/pds/src/lexicon/types/com/atproto/repo/createRecord.ts index 666b91c828d..55cc95d0ad7 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/createRecord.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/createRecord.ts @@ -11,15 +11,15 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ + /** The handle or DID of the repo (aka, current account). */ repo: string /** The NSID of the record collection. */ collection: string - /** The key of the record. */ + /** The Record Key. */ rkey?: string - /** Flag for validating the record. */ + /** Can be set to 'false' to skip Lexicon schema validation of record data. */ validate: boolean - /** The record to create. */ + /** The record itself. Must contain a $type field. */ record: {} /** Compare and swap with the previous commit by CID. */ swapCommit?: string diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/deleteRecord.ts b/packages/pds/src/lexicon/types/com/atproto/repo/deleteRecord.ts index 65ee32d213d..3bb97be0aad 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/deleteRecord.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/deleteRecord.ts @@ -11,11 +11,11 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ + /** The handle or DID of the repo (aka, current account). */ repo: string /** The NSID of the record collection. */ collection: string - /** The key of the record. */ + /** The Record Key. */ rkey: string /** Compare and swap with the previous record by CID. */ swapRecord?: string diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/describeRepo.ts b/packages/pds/src/lexicon/types/com/atproto/repo/describeRepo.ts index 38b9c01ef1c..749bedcfeb7 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/describeRepo.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/describeRepo.ts @@ -18,8 +18,11 @@ export type InputSchema = undefined export interface OutputSchema { handle: string did: string + /** The complete DID document for this account. */ didDoc: {} + /** List of all the collections (NSIDs) for which this repo contains at least one record. */ collections: string[] + /** Indicates if handle is currently valid (resolves bi-directionally) */ handleIsCorrect: boolean [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/getRecord.ts b/packages/pds/src/lexicon/types/com/atproto/repo/getRecord.ts index 345dde29a53..1a737a848be 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/getRecord.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/getRecord.ts @@ -13,7 +13,7 @@ export interface QueryParams { repo: string /** The NSID of the record collection. */ collection: string - /** The key of the record. */ + /** The Record Key. */ rkey: string /** The CID of the version of the record. If not specified, then return the most recent version. */ cid?: string diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/putRecord.ts b/packages/pds/src/lexicon/types/com/atproto/repo/putRecord.ts index de93e2e9cf7..193841a2294 100644 --- a/packages/pds/src/lexicon/types/com/atproto/repo/putRecord.ts +++ b/packages/pds/src/lexicon/types/com/atproto/repo/putRecord.ts @@ -11,17 +11,17 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** The handle or DID of the repo. */ + /** The handle or DID of the repo (aka, current account). */ repo: string /** The NSID of the record collection. */ collection: string - /** The key of the record. */ + /** The Record Key. */ rkey: string - /** Flag for validating the record. */ + /** Can be set to 'false' to skip Lexicon schema validation of record data. */ validate: boolean /** The record to write. */ record: {} - /** Compare and swap with the previous record by CID. */ + /** Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation */ swapRecord?: string | null /** Compare and swap with the previous commit by CID. */ swapCommit?: string diff --git a/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts b/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts index bceb61546cf..6e9b2f9f3c2 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/createAccount.ts @@ -12,22 +12,30 @@ export interface QueryParams {} export interface InputSchema { email?: string + /** Requested handle for the account. */ handle: string + /** Pre-existing atproto DID, being imported to a new account. */ did?: string inviteCode?: string verificationCode?: string verificationPhone?: string + /** Initial account password. May need to meet instance-specific password strength requirements. */ password?: string + /** DID PLC rotation key (aka, recovery key) to be included in PLC creation operation. */ recoveryKey?: string + /** A signed DID PLC operation to be submitted as part of importing an existing account to this instance. NOTE: this optional field may be updated when full account migration is implemented. */ plcOp?: {} [k: string]: unknown } +/** Account login session returned on successful account creation. */ export interface OutputSchema { accessJwt: string refreshJwt: string handle: string + /** The DID of the new account. */ did: string + /** Complete DID document. */ didDoc?: {} [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/com/atproto/server/createAppPassword.ts b/packages/pds/src/lexicon/types/com/atproto/server/createAppPassword.ts index 474846546fe..dcc5178ecfa 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/createAppPassword.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/createAppPassword.ts @@ -11,6 +11,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { + /** A short name for the App Password, to help distinguish them. */ name: string [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/com/atproto/server/describeServer.ts b/packages/pds/src/lexicon/types/com/atproto/server/describeServer.ts index 47be5b598b0..0e64c9d9708 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/describeServer.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/describeServer.ts @@ -13,8 +13,11 @@ export interface QueryParams {} export type InputSchema = undefined export interface OutputSchema { + /** If true, an invite code must be supplied to create an account on this instance. */ inviteCodeRequired?: boolean + /** If true, a phone verification token must be supplied to create an account on this instance. */ phoneVerificationRequired?: boolean + /** List of domain suffixes that can be used in account handles. */ availableUserDomains: string[] links?: Links [k: string]: unknown diff --git a/packages/pds/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts b/packages/pds/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts index 2dc551a477c..82c3ffa8c31 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/getAccountInviteCodes.ts @@ -11,6 +11,7 @@ import * as ComAtprotoServerDefs from './defs' export interface QueryParams { includeUsed: boolean + /** Controls whether any new 'earned' but not 'created' invites should be created. */ createAvailable: boolean } diff --git a/packages/pds/src/lexicon/types/com/atproto/server/reserveSigningKey.ts b/packages/pds/src/lexicon/types/com/atproto/server/reserveSigningKey.ts index 0de1220f4b5..0ec1e80c77c 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/reserveSigningKey.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/reserveSigningKey.ts @@ -11,13 +11,13 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** The did to reserve a new did:key for */ + /** The DID to reserve a key for. */ did?: string [k: string]: unknown } export interface OutputSchema { - /** Public signing key in the form of a did:key. */ + /** The public key for the reserved signing key, in did:key serialization. */ signingKey: string [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/getBlob.ts b/packages/pds/src/lexicon/types/com/atproto/sync/getBlob.ts index b3980fca500..93e50403f20 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/getBlob.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/getBlob.ts @@ -10,7 +10,7 @@ import { CID } from 'multiformats/cid' import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { - /** The DID of the repo. */ + /** The DID of the account. */ did: string /** The CID of the blob to fetch */ cid: string diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/getRecord.ts b/packages/pds/src/lexicon/types/com/atproto/sync/getRecord.ts index e27878ff5e6..c78ff8c2089 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/getRecord.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/getRecord.ts @@ -13,6 +13,7 @@ export interface QueryParams { /** The DID of the repo. */ did: string collection: string + /** Record Key */ rkey: string /** An optional past commit CID. */ commit?: string diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/getRepo.ts b/packages/pds/src/lexicon/types/com/atproto/sync/getRepo.ts index e0ad53ded7c..0d426557c5f 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/getRepo.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/getRepo.ts @@ -12,7 +12,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams { /** The DID of the repo. */ did: string - /** The revision of the repo to catch up from. */ + /** The revision ('rev') of the repo to create a diff from. */ since?: string } diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/listRepos.ts b/packages/pds/src/lexicon/types/com/atproto/sync/listRepos.ts index 12532860895..e5a8e2ca9d6 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/listRepos.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/listRepos.ts @@ -48,6 +48,7 @@ export type Handler = ( export interface Repo { did: string + /** Current repo commit CID */ head: string rev: string [k: string]: unknown diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts b/packages/pds/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts index f9498e6691d..8a0af577c7c 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/notifyOfUpdate.ts @@ -11,7 +11,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** Hostname of the service that is notifying of update. */ + /** Hostname of the current service (usually a PDS) that is notifying of update. */ hostname: string [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/requestCrawl.ts b/packages/pds/src/lexicon/types/com/atproto/sync/requestCrawl.ts index 2859e28fe69..31180aabf58 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/requestCrawl.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/requestCrawl.ts @@ -11,7 +11,7 @@ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' export interface QueryParams {} export interface InputSchema { - /** Hostname of the service that is requesting to be crawled. */ + /** Hostname of the current service (eg, PDS) that is requesting to be crawled. */ hostname: string [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/subscribeRepos.ts b/packages/pds/src/lexicon/types/com/atproto/sync/subscribeRepos.ts index fb334778bf6..689ea76daee 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/subscribeRepos.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/subscribeRepos.ts @@ -9,7 +9,7 @@ import { HandlerAuth, ErrorFrame } from '@atproto/xrpc-server' import { IncomingMessage } from 'http' export interface QueryParams { - /** The last known event to backfill from. */ + /** The last known event seq number to backfill from. */ cursor?: number } @@ -32,21 +32,29 @@ export type Handler = ( ctx: HandlerReqCtx, ) => AsyncIterable +/** Represents an update of repository state. Note that empty commits are allowed, which include no repo data changes, but an update to rev and signature. */ export interface Commit { + /** The stream sequence number of this message. */ seq: number + /** DEPRECATED -- unused */ rebase: boolean + /** Indicates that this commit contained too many ops, or data size was too large. Consumers will need to make a separate request to get missing data. */ tooBig: boolean + /** The repo this event comes from. */ repo: string + /** Repo commit object CID. */ commit: CID + /** DEPRECATED -- unused. WARNING -- nullable and optional; stick with optional to ensure golang interoperability. */ prev?: CID | null - /** The rev of the emitted commit. */ + /** The rev of the emitted commit. Note that this information is also in the commit object included in blocks, unless this is a tooBig event. */ rev: string - /** The rev of the last emitted commit from this repo. */ + /** The rev of the last emitted commit from this repo (if any). */ since: string | null - /** CAR file containing relevant blocks. */ + /** CAR file containing relevant blocks, as a diff since the previous repo state. */ blocks: Uint8Array ops: RepoOp[] blobs: CID[] + /** Timestamp of when this message was originally broadcast. */ time: string [k: string]: unknown } @@ -63,6 +71,7 @@ export function validateCommit(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#commit', v) } +/** Represents an update of the account's handle, or transition to/from invalid state. */ export interface Handle { seq: number did: string @@ -83,6 +92,7 @@ export function validateHandle(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#handle', v) } +/** Represents an account moving from one PDS instance to another. NOTE: not implemented; full account migration may introduce a new message instead. */ export interface Migrate { seq: number did: string @@ -103,6 +113,7 @@ export function validateMigrate(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#migrate', v) } +/** Indicates that an account has been deleted. */ export interface Tombstone { seq: number did: string @@ -140,10 +151,11 @@ export function validateInfo(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#info', v) } -/** A repo operation, ie a write of a single record. For creates and updates, CID is the record's CID as of this operation. For deletes, it's null. */ +/** A repo operation, ie a mutation of a single record. */ export interface RepoOp { action: 'create' | 'update' | 'delete' | (string & {}) path: string + /** For creates and updates, the new record CID. For deletions, null. */ cid: CID | null [k: string]: unknown } From 9360e246b59104d557d20f19df741743cc76c092 Mon Sep 17 00:00:00 2001 From: surfdude29 <149612116+surfdude29@users.noreply.github.com> Date: Tue, 13 Feb 2024 05:24:20 +0000 Subject: [PATCH 007/103] Update `blueskyweb.xyz` links in README.md to `bsky.social` (#2168) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a54fa91fe5f..96b7ee350f2 100644 --- a/README.md +++ b/README.md @@ -61,12 +61,12 @@ make help ## About AT Protocol -The Authenticated Transfer Protocol ("ATP" or "atproto") is a decentralized social media protocol, developed by [Bluesky PBC](https://blueskyweb.xyz). Learn more at: +The Authenticated Transfer Protocol ("ATP" or "atproto") is a decentralized social media protocol, developed by [Bluesky PBC](https://bsky.social). Learn more at: - [Overview and Guides](https://atproto.com/guides/overview) 👈 Best starting point - [Github Discussions](https://github.com/bluesky-social/atproto/discussions) 👈 Great place to ask questions - [Protocol Specifications](https://atproto.com/specs/atp) -- [Blogpost on self-authenticating data structures](https://blueskyweb.xyz/blog/3-6-2022-a-self-authenticating-social-protocol) +- [Blogpost on self-authenticating data structures](https://bsky.social/about/blog/3-6-2022-a-self-authenticating-social-protocol) The Bluesky Social application encompasses a set of schemas and APIs built in the overall AT Protocol framework. The namespace for these "Lexicons" is `app.bsky.*`. From b400fae24e38516bdf9ac3ae7fdb6038617fff73 Mon Sep 17 00:00:00 2001 From: Devin Ivy Date: Tue, 13 Feb 2024 11:28:19 -0500 Subject: [PATCH 008/103] sanity check on did part --- .github/workflows/build-and-push-pds-ghcr.yaml | 2 +- lexicons/com/atproto/server/reserveSigningKey.json | 1 + packages/api/src/client/lexicons.ts | 1 + packages/bsky/src/lexicon/lexicons.ts | 1 + packages/ozone/src/lexicon/lexicons.ts | 1 + packages/pds/src/actor-store/index.ts | 13 +++++++++++++ packages/pds/src/lexicon/lexicons.ts | 1 + 7 files changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-push-pds-ghcr.yaml b/.github/workflows/build-and-push-pds-ghcr.yaml index 28932c5959b..422894a1bd2 100644 --- a/.github/workflows/build-and-push-pds-ghcr.yaml +++ b/.github/workflows/build-and-push-pds-ghcr.yaml @@ -3,7 +3,7 @@ on: push: branches: - main - - pds-node-v20 + - pds-sanity-check env: REGISTRY: ghcr.io USERNAME: ${{ github.actor }} diff --git a/lexicons/com/atproto/server/reserveSigningKey.json b/lexicons/com/atproto/server/reserveSigningKey.json index 4a99a34528b..a33e1ede68e 100644 --- a/lexicons/com/atproto/server/reserveSigningKey.json +++ b/lexicons/com/atproto/server/reserveSigningKey.json @@ -12,6 +12,7 @@ "properties": { "did": { "type": "string", + "format": "did", "description": "The DID to reserve a key for." } } diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index 8fcf9491077..c8a94315c0d 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -3643,6 +3643,7 @@ export const schemaDict = { properties: { did: { type: 'string', + format: 'did', description: 'The DID to reserve a key for.', }, }, diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index 8fcf9491077..c8a94315c0d 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -3643,6 +3643,7 @@ export const schemaDict = { properties: { did: { type: 'string', + format: 'did', description: 'The DID to reserve a key for.', }, }, diff --git a/packages/ozone/src/lexicon/lexicons.ts b/packages/ozone/src/lexicon/lexicons.ts index 8fcf9491077..c8a94315c0d 100644 --- a/packages/ozone/src/lexicon/lexicons.ts +++ b/packages/ozone/src/lexicon/lexicons.ts @@ -3643,6 +3643,7 @@ export const schemaDict = { properties: { did: { type: 'string', + format: 'did', description: 'The DID to reserve a key for.', }, }, diff --git a/packages/pds/src/actor-store/index.ts b/packages/pds/src/actor-store/index.ts index b18716380e6..1ac7ea52bb3 100644 --- a/packages/pds/src/actor-store/index.ts +++ b/packages/pds/src/actor-store/index.ts @@ -1,4 +1,5 @@ import path from 'path' +import assert from 'assert' import fs from 'fs/promises' import * as crypto from '@atproto/crypto' import { Keypair, ExportableKeypair } from '@atproto/crypto' @@ -148,6 +149,7 @@ export class ActorStore { async reserveKeypair(did?: string): Promise { let keyLoc: string | undefined if (did) { + assertSafePathPart(did) keyLoc = path.join(this.reservedKeyDir, did) const maybeKey = await loadKey(keyLoc) if (maybeKey) { @@ -259,3 +261,14 @@ export type ActorStoreTransactor = { record: RecordTransactor pref: PreferenceTransactor } + +function assertSafePathPart(part: string) { + const normalized = path.normalize(part) + assert( + part === normalized && + !part.startsWith('.') && + !part.includes('/') && + !part.includes('\\'), + `unsafe path part: ${part}`, + ) +} diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index 8fcf9491077..c8a94315c0d 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -3643,6 +3643,7 @@ export const schemaDict = { properties: { did: { type: 'string', + format: 'did', description: 'The DID to reserve a key for.', }, }, From 0acd6c180c16a08d3c05356f4e212fca938b6bbc Mon Sep 17 00:00:00 2001 From: Foysal Ahamed Date: Tue, 13 Feb 2024 21:20:54 +0100 Subject: [PATCH 009/103] Include takendown posts on author feed for admins (#2172) * :sparkles: Allow admins to view author feed on takendown account * :white_check_mark: Add tests for admin behavior --- .../src/api/app/bsky/feed/getAuthorFeed.ts | 16 ++++++-- packages/bsky/src/services/feed/index.ts | 14 +++++-- packages/bsky/src/services/feed/views.ts | 4 +- packages/bsky/tests/views/author-feed.test.ts | 38 ++++++++++++++----- 4 files changed, 54 insertions(+), 18 deletions(-) diff --git a/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts b/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts index 6c783efdd0c..6b344547a45 100644 --- a/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts +++ b/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts @@ -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), @@ -53,12 +57,12 @@ export const skeleton = async ( params: Params, ctx: Context, ): Promise => { - 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') } @@ -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 } } @@ -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 diff --git a/packages/bsky/src/services/feed/index.ts b/packages/bsky/src/services/feed/index.ts index a8768518d70..9b94fd6c714 100644 --- a/packages/bsky/src/services/feed/index.ts +++ b/packages/bsky/src/services/feed/index.ts @@ -139,6 +139,7 @@ export class FeedService { async getPostInfos( postUris: string[], viewer: string | null, + includeSoftDeleted?: boolean, ): Promise { if (postUris.length < 1) return {} const db = this.db.db @@ -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', @@ -249,12 +254,13 @@ export class FeedService { dids: Set uris: Set viewer: string | null + includeSoftDeleted?: boolean }, depth = 0, ): Promise { 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( @@ -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), diff --git a/packages/bsky/src/services/feed/views.ts b/packages/bsky/src/services/feed/views.ts index 7f9fd12e082..ac733032acd 100644 --- a/packages/bsky/src/services/feed/views.ts +++ b/packages/bsky/src/services/feed/views.ts @@ -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, diff --git a/packages/bsky/tests/views/author-feed.test.ts b/packages/bsky/tests/views/author-feed.test.ts index 4db9ee49028..b4644d49f67 100644 --- a/packages/bsky/tests/views/author-feed.test.ts +++ b/packages/bsky/tests/views/author-feed.test.ts @@ -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) }, @@ -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( @@ -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( From 5f9ff1f17fd699b1c0f7944171e1d0e033c40332 Mon Sep 17 00:00:00 2001 From: Christian De Angelis Date: Wed, 14 Feb 2024 02:01:08 -0500 Subject: [PATCH 010/103] Remove duplicate description in app.bsky.feed.post (#2183) --- lexicons/app/bsky/feed/post.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lexicons/app/bsky/feed/post.json b/lexicons/app/bsky/feed/post.json index 1e3848c4569..b9b236b4f81 100644 --- a/lexicons/app/bsky/feed/post.json +++ b/lexicons/app/bsky/feed/post.json @@ -51,8 +51,7 @@ "type": "array", "description": "Additional hashtags, in addition to any included in post text and facets.", "maxLength": 8, - "items": { "type": "string", "maxLength": 640, "maxGraphemes": 64 }, - "description": "Additional non-inline tags describing this post." + "items": { "type": "string", "maxLength": 640, "maxGraphemes": 64 } }, "createdAt": { "type": "string", From fcf8e3faf311559162c3aa0d9af36f84951914bc Mon Sep 17 00:00:00 2001 From: bnewbold Date: Wed, 14 Feb 2024 18:15:17 -0800 Subject: [PATCH 011/103] repo: commit prev as nullable, but non-optional (#2173) * repo: commit prev as nullable, but non-optional * changeset --- .changeset/angry-chicken-float.md | 5 +++++ packages/repo/src/types.ts | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/angry-chicken-float.md diff --git a/.changeset/angry-chicken-float.md b/.changeset/angry-chicken-float.md new file mode 100644 index 00000000000..5759975d848 --- /dev/null +++ b/.changeset/angry-chicken-float.md @@ -0,0 +1,5 @@ +--- +'@atproto/repo': patch +--- + +repo commit object prev field is nullable, but no longer nullable diff --git a/packages/repo/src/types.ts b/packages/repo/src/types.ts index 7aeaba03fca..e9debe013ed 100644 --- a/packages/repo/src/types.ts +++ b/packages/repo/src/types.ts @@ -16,7 +16,7 @@ const unsignedCommit = z.object({ data: common.cid, rev: z.string(), // `prev` added for backwards compatibility with v2, no requirement of keeping around history - prev: common.cid.nullable().optional(), + prev: common.cid.nullable(), }) export type UnsignedCommit = z.infer & { sig?: never } @@ -25,7 +25,7 @@ const commit = z.object({ version: z.literal(3), data: common.cid, rev: z.string(), - prev: common.cid.nullable().optional(), + prev: common.cid.nullable(), sig: common.bytes, }) export type Commit = z.infer From 8c94979f73fc5057449e24e66ef2e09b0e17e55b Mon Sep 17 00:00:00 2001 From: Daniel Holmgren Date: Thu, 15 Feb 2024 20:12:39 -0600 Subject: [PATCH 012/103] Timeline index pref (#2188) * add following index pref * added changeset * change pref name * update changeset --- .changeset/silly-ligers-poke.md | 5 +++++ lexicons/app/bsky/actor/defs.json | 3 +++ packages/api/src/client/lexicons.ts | 3 +++ packages/api/src/client/types/app/bsky/actor/defs.ts | 1 + packages/bsky/src/lexicon/lexicons.ts | 3 +++ packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts | 1 + packages/ozone/src/lexicon/lexicons.ts | 3 +++ packages/ozone/src/lexicon/types/app/bsky/actor/defs.ts | 1 + packages/pds/src/lexicon/lexicons.ts | 3 +++ packages/pds/src/lexicon/types/app/bsky/actor/defs.ts | 1 + 10 files changed, 24 insertions(+) create mode 100644 .changeset/silly-ligers-poke.md diff --git a/.changeset/silly-ligers-poke.md b/.changeset/silly-ligers-poke.md new file mode 100644 index 00000000000..81a4270f772 --- /dev/null +++ b/.changeset/silly-ligers-poke.md @@ -0,0 +1,5 @@ +--- +'@atproto/api': patch +--- + +Added timelineIndex to savedFeedsPref diff --git a/lexicons/app/bsky/actor/defs.json b/lexicons/app/bsky/actor/defs.json index b4499bcb7cc..fa3772c4ff3 100644 --- a/lexicons/app/bsky/actor/defs.json +++ b/lexicons/app/bsky/actor/defs.json @@ -144,6 +144,9 @@ "type": "string", "format": "at-uri" } + }, + "timelineIndex": { + "type": "integer" } } }, diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index c8a94315c0d..a13f5fbf1f2 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -4833,6 +4833,9 @@ export const schemaDict = { format: 'at-uri', }, }, + timelineIndex: { + type: 'integer', + }, }, }, personalDetailsPref: { diff --git a/packages/api/src/client/types/app/bsky/actor/defs.ts b/packages/api/src/client/types/app/bsky/actor/defs.ts index 2e58d890b72..5f493576334 100644 --- a/packages/api/src/client/types/app/bsky/actor/defs.ts +++ b/packages/api/src/client/types/app/bsky/actor/defs.ts @@ -155,6 +155,7 @@ export function validateContentLabelPref(v: unknown): ValidationResult { export interface SavedFeedsPref { pinned: string[] saved: string[] + timelineIndex?: number [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index c8a94315c0d..a13f5fbf1f2 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -4833,6 +4833,9 @@ export const schemaDict = { format: 'at-uri', }, }, + timelineIndex: { + type: 'integer', + }, }, }, personalDetailsPref: { diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts index ff49c40dc0b..a802f70f44b 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts @@ -155,6 +155,7 @@ export function validateContentLabelPref(v: unknown): ValidationResult { export interface SavedFeedsPref { pinned: string[] saved: string[] + timelineIndex?: number [k: string]: unknown } diff --git a/packages/ozone/src/lexicon/lexicons.ts b/packages/ozone/src/lexicon/lexicons.ts index c8a94315c0d..a13f5fbf1f2 100644 --- a/packages/ozone/src/lexicon/lexicons.ts +++ b/packages/ozone/src/lexicon/lexicons.ts @@ -4833,6 +4833,9 @@ export const schemaDict = { format: 'at-uri', }, }, + timelineIndex: { + type: 'integer', + }, }, }, personalDetailsPref: { diff --git a/packages/ozone/src/lexicon/types/app/bsky/actor/defs.ts b/packages/ozone/src/lexicon/types/app/bsky/actor/defs.ts index ff49c40dc0b..a802f70f44b 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/actor/defs.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/actor/defs.ts @@ -155,6 +155,7 @@ export function validateContentLabelPref(v: unknown): ValidationResult { export interface SavedFeedsPref { pinned: string[] saved: string[] + timelineIndex?: number [k: string]: unknown } diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index c8a94315c0d..a13f5fbf1f2 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -4833,6 +4833,9 @@ export const schemaDict = { format: 'at-uri', }, }, + timelineIndex: { + type: 'integer', + }, }, }, personalDetailsPref: { diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts b/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts index ff49c40dc0b..a802f70f44b 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts @@ -155,6 +155,7 @@ export function validateContentLabelPref(v: unknown): ValidationResult { export interface SavedFeedsPref { pinned: string[] saved: string[] + timelineIndex?: number [k: string]: unknown } From 6e6b94be10b4cb541792cf7bbaf1ea6944116939 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 15 Feb 2024 20:22:33 -0600 Subject: [PATCH 013/103] Version packages (#2184) Co-authored-by: github-actions[bot] --- .changeset/angry-chicken-float.md | 5 ----- .changeset/silly-ligers-poke.md | 5 ----- packages/api/CHANGELOG.md | 6 ++++++ packages/api/package.json | 2 +- packages/aws/CHANGELOG.md | 7 +++++++ packages/aws/package.json | 2 +- packages/bsky/CHANGELOG.md | 8 ++++++++ packages/bsky/package.json | 2 +- packages/dev-env/CHANGELOG.md | 10 ++++++++++ packages/dev-env/package.json | 2 +- packages/ozone/CHANGELOG.md | 7 +++++++ packages/ozone/package.json | 2 +- packages/pds/CHANGELOG.md | 9 +++++++++ packages/pds/package.json | 2 +- packages/repo/CHANGELOG.md | 6 ++++++ packages/repo/package.json | 2 +- 16 files changed, 60 insertions(+), 17 deletions(-) delete mode 100644 .changeset/angry-chicken-float.md delete mode 100644 .changeset/silly-ligers-poke.md diff --git a/.changeset/angry-chicken-float.md b/.changeset/angry-chicken-float.md deleted file mode 100644 index 5759975d848..00000000000 --- a/.changeset/angry-chicken-float.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@atproto/repo': patch ---- - -repo commit object prev field is nullable, but no longer nullable diff --git a/.changeset/silly-ligers-poke.md b/.changeset/silly-ligers-poke.md deleted file mode 100644 index 81a4270f772..00000000000 --- a/.changeset/silly-ligers-poke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@atproto/api': patch ---- - -Added timelineIndex to savedFeedsPref diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md index 807d8b791e8..dfb163df8b6 100644 --- a/packages/api/CHANGELOG.md +++ b/packages/api/CHANGELOG.md @@ -1,5 +1,11 @@ # @atproto/api +## 0.9.7 + +### Patch Changes + +- [#2188](https://github.com/bluesky-social/atproto/pull/2188) [`8c94979f7`](https://github.com/bluesky-social/atproto/commit/8c94979f73fc5057449e24e66ef2e09b0e17e55b) Thanks [@dholms](https://github.com/dholms)! - Added timelineIndex to savedFeedsPref + ## 0.9.6 ### Patch Changes diff --git a/packages/api/package.json b/packages/api/package.json index 55ee2f5b2d5..71221da038a 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/api", - "version": "0.9.6", + "version": "0.9.7", "license": "MIT", "description": "Client library for atproto and Bluesky", "keywords": [ diff --git a/packages/aws/CHANGELOG.md b/packages/aws/CHANGELOG.md index b804e0719e4..f8f0c521f18 100644 --- a/packages/aws/CHANGELOG.md +++ b/packages/aws/CHANGELOG.md @@ -1,5 +1,12 @@ # @atproto/aws +## 0.1.7 + +### Patch Changes + +- Updated dependencies [[`fcf8e3faf`](https://github.com/bluesky-social/atproto/commit/fcf8e3faf311559162c3aa0d9af36f84951914bc)]: + - @atproto/repo@0.3.7 + ## 0.1.6 ### Patch Changes diff --git a/packages/aws/package.json b/packages/aws/package.json index 949cfaa845e..55638b88552 100644 --- a/packages/aws/package.json +++ b/packages/aws/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/aws", - "version": "0.1.6", + "version": "0.1.7", "license": "MIT", "description": "Shared AWS cloud API helpers for atproto services", "keywords": [ diff --git a/packages/bsky/CHANGELOG.md b/packages/bsky/CHANGELOG.md index 426ee79acf2..070c81fef28 100644 --- a/packages/bsky/CHANGELOG.md +++ b/packages/bsky/CHANGELOG.md @@ -1,5 +1,13 @@ # @atproto/bsky +## 0.0.30 + +### Patch Changes + +- Updated dependencies [[`fcf8e3faf`](https://github.com/bluesky-social/atproto/commit/fcf8e3faf311559162c3aa0d9af36f84951914bc), [`8c94979f7`](https://github.com/bluesky-social/atproto/commit/8c94979f73fc5057449e24e66ef2e09b0e17e55b)]: + - @atproto/repo@0.3.7 + - @atproto/api@0.9.7 + ## 0.0.29 ### Patch Changes diff --git a/packages/bsky/package.json b/packages/bsky/package.json index ac301188e00..f974353079e 100644 --- a/packages/bsky/package.json +++ b/packages/bsky/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/bsky", - "version": "0.0.29", + "version": "0.0.30", "license": "MIT", "description": "Reference implementation of app.bsky App View (Bluesky API)", "keywords": [ diff --git a/packages/dev-env/CHANGELOG.md b/packages/dev-env/CHANGELOG.md index 132a071c68b..004fa2029c1 100644 --- a/packages/dev-env/CHANGELOG.md +++ b/packages/dev-env/CHANGELOG.md @@ -1,5 +1,15 @@ # @atproto/dev-env +## 0.2.30 + +### Patch Changes + +- Updated dependencies [[`8c94979f7`](https://github.com/bluesky-social/atproto/commit/8c94979f73fc5057449e24e66ef2e09b0e17e55b)]: + - @atproto/api@0.9.7 + - @atproto/bsky@0.0.30 + - @atproto/pds@0.3.18 + - @atproto/ozone@0.0.9 + ## 0.2.29 ### Patch Changes diff --git a/packages/dev-env/package.json b/packages/dev-env/package.json index 87cfc87882a..d2a7bbfda72 100644 --- a/packages/dev-env/package.json +++ b/packages/dev-env/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/dev-env", - "version": "0.2.29", + "version": "0.2.30", "license": "MIT", "description": "Local development environment helper for atproto development", "keywords": [ diff --git a/packages/ozone/CHANGELOG.md b/packages/ozone/CHANGELOG.md index 0a9503ab56e..4af716d59a9 100644 --- a/packages/ozone/CHANGELOG.md +++ b/packages/ozone/CHANGELOG.md @@ -1,5 +1,12 @@ # @atproto/ozone +## 0.0.9 + +### Patch Changes + +- Updated dependencies [[`8c94979f7`](https://github.com/bluesky-social/atproto/commit/8c94979f73fc5057449e24e66ef2e09b0e17e55b)]: + - @atproto/api@0.9.7 + ## 0.0.8 ### Patch Changes diff --git a/packages/ozone/package.json b/packages/ozone/package.json index 6656a740e5c..1e91ca18bbf 100644 --- a/packages/ozone/package.json +++ b/packages/ozone/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/ozone", - "version": "0.0.8", + "version": "0.0.9", "license": "MIT", "description": "Backend service for moderating the Bluesky network.", "keywords": [ diff --git a/packages/pds/CHANGELOG.md b/packages/pds/CHANGELOG.md index 492741aa228..a4e6c638010 100644 --- a/packages/pds/CHANGELOG.md +++ b/packages/pds/CHANGELOG.md @@ -1,5 +1,14 @@ # @atproto/pds +## 0.3.18 + +### Patch Changes + +- Updated dependencies [[`fcf8e3faf`](https://github.com/bluesky-social/atproto/commit/fcf8e3faf311559162c3aa0d9af36f84951914bc), [`8c94979f7`](https://github.com/bluesky-social/atproto/commit/8c94979f73fc5057449e24e66ef2e09b0e17e55b)]: + - @atproto/repo@0.3.7 + - @atproto/api@0.9.7 + - @atproto/aws@0.1.7 + ## 0.3.17 ### Patch Changes diff --git a/packages/pds/package.json b/packages/pds/package.json index 4bc30f5fade..48ba1356774 100644 --- a/packages/pds/package.json +++ b/packages/pds/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/pds", - "version": "0.3.17", + "version": "0.3.18", "license": "MIT", "description": "Reference implementation of atproto Personal Data Server (PDS)", "keywords": [ diff --git a/packages/repo/CHANGELOG.md b/packages/repo/CHANGELOG.md index 448005d16d2..4a52264ad23 100644 --- a/packages/repo/CHANGELOG.md +++ b/packages/repo/CHANGELOG.md @@ -1,5 +1,11 @@ # @atproto/repo +## 0.3.7 + +### Patch Changes + +- [#2173](https://github.com/bluesky-social/atproto/pull/2173) [`fcf8e3faf`](https://github.com/bluesky-social/atproto/commit/fcf8e3faf311559162c3aa0d9af36f84951914bc) Thanks [@bnewbold](https://github.com/bnewbold)! - repo commit object prev field is nullable, but no longer nullable + ## 0.3.6 ### Patch Changes diff --git a/packages/repo/package.json b/packages/repo/package.json index b6a3b87607e..9badb9f9a42 100644 --- a/packages/repo/package.json +++ b/packages/repo/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/repo", - "version": "0.3.6", + "version": "0.3.7", "license": "MIT", "description": "atproto repo and MST implementation", "keywords": [ From d81573232ba3e3074c4afdb4c747f9426379420b Mon Sep 17 00:00:00 2001 From: Foysal Ahamed Date: Mon, 19 Feb 2024 16:35:03 +0100 Subject: [PATCH 014/103] Allow filtering moderation queue by language (#2161) * :sparkles: Store languages on subjects from record data * :sparkles: Fetch lang from author feed for repo subject * :sparkles: Regenerate lex * :sparkles: Add lang to profile subject * :sparkles: Move lang to flag * :sparkles: Add comment to flag event * :white_check_mark: Update pds test snapshot * :bug: Fix broken import * :sparkles: Rename flag to tag * :white_check_mark: Update snapshot for event by id * :white_check_mark: Bring back skipped test * :sparkles: Move tags to dedicated columns * :white_check_mark: Update test snapshots, use logger * :sparkles: Change lang:unknown to lang:und * :white_check_mark: Update test snapshots * :broom: Cleanup * :sparkles: Add filter params for tags on queryModerationEvents endpoint * :sparkles: Add tags property to subject status model * :white_check_mark: Update test code * :sparkles: Add filter to get subjects that do not have certain tags --- lexicons/com/atproto/admin/defs.json | 25 +++++ .../atproto/admin/emitModerationEvent.json | 3 +- .../atproto/admin/queryModerationEvents.json | 10 ++ .../admin/queryModerationStatuses.json | 8 ++ packages/api/src/client/lexicons.ts | 62 +++++++++++++ .../client/types/com/atproto/admin/defs.ts | 24 +++++ .../com/atproto/admin/emitModerationEvent.ts | 1 + .../atproto/admin/queryModerationEvents.ts | 4 + .../atproto/admin/queryModerationStatuses.ts | 2 + packages/bsky/src/lexicon/lexicons.ts | 62 +++++++++++++ .../lexicon/types/com/atproto/admin/defs.ts | 24 +++++ .../com/atproto/admin/emitModerationEvent.ts | 1 + .../atproto/admin/queryModerationEvents.ts | 4 + .../atproto/admin/queryModerationStatuses.ts | 2 + .../bsky/tests/auto-moderator/labeler.test.ts | 2 + packages/ozone/package.json | 2 +- .../src/api/admin/emitModerationEvent.ts | 32 +++++-- .../src/api/admin/queryModerationEvents.ts | 4 + .../src/api/admin/queryModerationStatuses.ts | 4 + .../ozone/src/api/moderation/createReport.ts | 19 +++- packages/ozone/src/api/moderation/util.ts | 1 + ...Z-add-tags-column-to-moderation-subject.ts | 31 +++++++ packages/ozone/src/db/migrations/index.ts | 1 + .../ozone/src/db/schema/moderation_event.ts | 3 + .../db/schema/moderation_subject_status.ts | 1 + packages/ozone/src/lexicon/lexicons.ts | 62 +++++++++++++ .../lexicon/types/com/atproto/admin/defs.ts | 24 +++++ .../com/atproto/admin/emitModerationEvent.ts | 1 + .../atproto/admin/queryModerationEvents.ts | 4 + .../atproto/admin/queryModerationStatuses.ts | 2 + packages/ozone/src/logger.ts | 2 + packages/ozone/src/mod-service/index.ts | 70 ++++++++++++-- packages/ozone/src/mod-service/lang.ts | 82 +++++++++++++++++ packages/ozone/src/mod-service/status.ts | 22 ++++- packages/ozone/src/mod-service/types.ts | 1 + packages/ozone/src/mod-service/views.ts | 23 ++++- .../__snapshots__/get-record.test.ts.snap | 6 ++ .../tests/__snapshots__/get-repo.test.ts.snap | 3 + .../moderation-events.test.ts.snap | 49 +++++++++- .../moderation-statuses.test.ts.snap | 62 ++++++++++++- .../__snapshots__/moderation.test.ts.snap | 6 +- packages/ozone/tests/get-record.test.ts | 8 -- .../ozone/tests/moderation-events.test.ts | 62 ++++++++++++- .../tests/moderation-status-tags.test.ts | 92 +++++++++++++++++++ .../ozone/tests/moderation-statuses.test.ts | 23 ++++- packages/pds/src/lexicon/lexicons.ts | 62 +++++++++++++ .../lexicon/types/com/atproto/admin/defs.ts | 24 +++++ .../com/atproto/admin/emitModerationEvent.ts | 1 + .../atproto/admin/queryModerationEvents.ts | 4 + .../atproto/admin/queryModerationStatuses.ts | 2 + .../proxied/__snapshots__/admin.test.ts.snap | 63 +++++++++++-- pnpm-lock.yaml | 8 +- 52 files changed, 1031 insertions(+), 69 deletions(-) create mode 100644 packages/ozone/src/db/migrations/20240208T213404429Z-add-tags-column-to-moderation-subject.ts create mode 100644 packages/ozone/src/mod-service/lang.ts create mode 100644 packages/ozone/tests/moderation-status-tags.test.ts diff --git a/lexicons/com/atproto/admin/defs.json b/lexicons/com/atproto/admin/defs.json index 133deaf9383..e1315eb7473 100644 --- a/lexicons/com/atproto/admin/defs.json +++ b/lexicons/com/atproto/admin/defs.json @@ -185,6 +185,10 @@ "suspendUntil": { "type": "string", "format": "datetime" + }, + "tags": { + "type": "array", + "items": { "type": "string" } } } }, @@ -587,6 +591,27 @@ } } }, + "modEventTag": { + "type": "object", + "description": "Add/Remove a tag on a subject", + "required": ["add", "remove"], + "properties": { + "add": { + "type": "array", + "items": { "type": "string" }, + "description": "Tags to be added to the subject. If already exists, won't be duplicated." + }, + "remove": { + "type": "array", + "items": { "type": "string" }, + "description": "Tags to be removed to the subject. Ignores a tag If it doesn't exist, won't be duplicated." + }, + "comment": { + "type": "string", + "description": "Additional comment about added/removed tags." + } + } + }, "communicationTemplateView": { "type": "object", "required": [ diff --git a/lexicons/com/atproto/admin/emitModerationEvent.json b/lexicons/com/atproto/admin/emitModerationEvent.json index f32ad18461c..44ef72aad5b 100644 --- a/lexicons/com/atproto/admin/emitModerationEvent.json +++ b/lexicons/com/atproto/admin/emitModerationEvent.json @@ -23,7 +23,8 @@ "com.atproto.admin.defs#modEventMute", "com.atproto.admin.defs#modEventReverseTakedown", "com.atproto.admin.defs#modEventUnmute", - "com.atproto.admin.defs#modEventEmail" + "com.atproto.admin.defs#modEventEmail", + "com.atproto.admin.defs#modEventTag" ] }, "subject": { diff --git a/lexicons/com/atproto/admin/queryModerationEvents.json b/lexicons/com/atproto/admin/queryModerationEvents.json index 887921cfe20..239c17bd115 100644 --- a/lexicons/com/atproto/admin/queryModerationEvents.json +++ b/lexicons/com/atproto/admin/queryModerationEvents.json @@ -63,6 +63,16 @@ "items": { "type": "string" }, "description": "If specified, only events where all of these labels were removed are returned" }, + "addedTags": { + "type": "array", + "items": { "type": "string" }, + "description": "If specified, only events where all of these tags were added are returned" + }, + "removedTags": { + "type": "array", + "items": { "type": "string" }, + "description": "If specified, only events where all of these tags were removed are returned" + }, "reportTypes": { "type": "array", "items": { diff --git a/lexicons/com/atproto/admin/queryModerationStatuses.json b/lexicons/com/atproto/admin/queryModerationStatuses.json index e3e2a859bd2..5ac915ceef1 100644 --- a/lexicons/com/atproto/admin/queryModerationStatuses.json +++ b/lexicons/com/atproto/admin/queryModerationStatuses.json @@ -74,6 +74,14 @@ "maximum": 100, "default": 50 }, + "tags": { + "type": "array", + "items": { "type": "string" } + }, + "excludeTags": { + "type": "array", + "items": { "type": "string" } + }, "cursor": { "type": "string" } } }, diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index a13f5fbf1f2..01a2f683d77 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -303,6 +303,12 @@ export const schemaDict = { type: 'string', format: 'datetime', }, + tags: { + type: 'array', + items: { + type: 'string', + }, + }, }, }, reportViewDetail: { @@ -897,6 +903,33 @@ export const schemaDict = { }, }, }, + modEventTag: { + type: 'object', + description: 'Add/Remove a tag on a subject', + required: ['add', 'remove'], + properties: { + add: { + type: 'array', + items: { + type: 'string', + }, + description: + "Tags to be added to the subject. If already exists, won't be duplicated.", + }, + remove: { + type: 'array', + items: { + type: 'string', + }, + description: + "Tags to be removed to the subject. Ignores a tag If it doesn't exist, won't be duplicated.", + }, + comment: { + type: 'string', + description: 'Additional comment about added/removed tags.', + }, + }, + }, communicationTemplateView: { type: 'object', required: [ @@ -1075,6 +1108,7 @@ export const schemaDict = { 'lex:com.atproto.admin.defs#modEventReverseTakedown', 'lex:com.atproto.admin.defs#modEventUnmute', 'lex:com.atproto.admin.defs#modEventEmail', + 'lex:com.atproto.admin.defs#modEventTag', ], }, subject: { @@ -1503,6 +1537,22 @@ export const schemaDict = { description: 'If specified, only events where all of these labels were removed are returned', }, + addedTags: { + type: 'array', + items: { + type: 'string', + }, + description: + 'If specified, only events where all of these tags were added are returned', + }, + removedTags: { + type: 'array', + items: { + type: 'string', + }, + description: + 'If specified, only events where all of these tags were removed are returned', + }, reportTypes: { type: 'array', items: { @@ -1620,6 +1670,18 @@ export const schemaDict = { maximum: 100, default: 50, }, + tags: { + type: 'array', + items: { + type: 'string', + }, + }, + excludeTags: { + type: 'array', + items: { + type: 'string', + }, + }, cursor: { type: 'string', }, diff --git a/packages/api/src/client/types/com/atproto/admin/defs.ts b/packages/api/src/client/types/com/atproto/admin/defs.ts index c0d8157e6ac..4e3d35a869f 100644 --- a/packages/api/src/client/types/com/atproto/admin/defs.ts +++ b/packages/api/src/client/types/com/atproto/admin/defs.ts @@ -156,6 +156,7 @@ export interface SubjectStatusView { /** True indicates that the a previously taken moderator action was appealed against, by the author of the content. False indicates last appeal was resolved by moderators. */ appealed?: boolean suspendUntil?: string + tags?: string[] [k: string]: unknown } @@ -720,6 +721,29 @@ export function validateModEventEmail(v: unknown): ValidationResult { return lexicons.validate('com.atproto.admin.defs#modEventEmail', v) } +/** Add/Remove a tag on a subject */ +export interface ModEventTag { + /** Tags to be added to the subject. If already exists, won't be duplicated. */ + add: string[] + /** Tags to be removed to the subject. Ignores a tag If it doesn't exist, won't be duplicated. */ + remove: string[] + /** Additional comment about added/removed tags. */ + comment?: string + [k: string]: unknown +} + +export function isModEventTag(v: unknown): v is ModEventTag { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'com.atproto.admin.defs#modEventTag' + ) +} + +export function validateModEventTag(v: unknown): ValidationResult { + return lexicons.validate('com.atproto.admin.defs#modEventTag', v) +} + export interface CommunicationTemplateView { id: string /** Name of the template. */ diff --git a/packages/api/src/client/types/com/atproto/admin/emitModerationEvent.ts b/packages/api/src/client/types/com/atproto/admin/emitModerationEvent.ts index 77b460ed1ff..6e7827bdc6a 100644 --- a/packages/api/src/client/types/com/atproto/admin/emitModerationEvent.ts +++ b/packages/api/src/client/types/com/atproto/admin/emitModerationEvent.ts @@ -23,6 +23,7 @@ export interface InputSchema { | ComAtprotoAdminDefs.ModEventReverseTakedown | ComAtprotoAdminDefs.ModEventUnmute | ComAtprotoAdminDefs.ModEventEmail + | ComAtprotoAdminDefs.ModEventTag | { $type: string; [k: string]: unknown } subject: | ComAtprotoAdminDefs.RepoRef diff --git a/packages/api/src/client/types/com/atproto/admin/queryModerationEvents.ts b/packages/api/src/client/types/com/atproto/admin/queryModerationEvents.ts index 1540babae51..2dd3081ef8f 100644 --- a/packages/api/src/client/types/com/atproto/admin/queryModerationEvents.ts +++ b/packages/api/src/client/types/com/atproto/admin/queryModerationEvents.ts @@ -30,6 +30,10 @@ export interface QueryParams { addedLabels?: string[] /** If specified, only events where all of these labels were removed are returned */ removedLabels?: string[] + /** If specified, only events where all of these tags were added are returned */ + addedTags?: string[] + /** If specified, only events where all of these tags were removed are returned */ + removedTags?: string[] reportTypes?: string[] cursor?: string } diff --git a/packages/api/src/client/types/com/atproto/admin/queryModerationStatuses.ts b/packages/api/src/client/types/com/atproto/admin/queryModerationStatuses.ts index 0039016a353..c16bb94fec3 100644 --- a/packages/api/src/client/types/com/atproto/admin/queryModerationStatuses.ts +++ b/packages/api/src/client/types/com/atproto/admin/queryModerationStatuses.ts @@ -34,6 +34,8 @@ export interface QueryParams { /** Get subjects in unresolved appealed status */ appealed?: boolean limit?: number + tags?: string[] + excludeTags?: string[] cursor?: string } diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index a13f5fbf1f2..01a2f683d77 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -303,6 +303,12 @@ export const schemaDict = { type: 'string', format: 'datetime', }, + tags: { + type: 'array', + items: { + type: 'string', + }, + }, }, }, reportViewDetail: { @@ -897,6 +903,33 @@ export const schemaDict = { }, }, }, + modEventTag: { + type: 'object', + description: 'Add/Remove a tag on a subject', + required: ['add', 'remove'], + properties: { + add: { + type: 'array', + items: { + type: 'string', + }, + description: + "Tags to be added to the subject. If already exists, won't be duplicated.", + }, + remove: { + type: 'array', + items: { + type: 'string', + }, + description: + "Tags to be removed to the subject. Ignores a tag If it doesn't exist, won't be duplicated.", + }, + comment: { + type: 'string', + description: 'Additional comment about added/removed tags.', + }, + }, + }, communicationTemplateView: { type: 'object', required: [ @@ -1075,6 +1108,7 @@ export const schemaDict = { 'lex:com.atproto.admin.defs#modEventReverseTakedown', 'lex:com.atproto.admin.defs#modEventUnmute', 'lex:com.atproto.admin.defs#modEventEmail', + 'lex:com.atproto.admin.defs#modEventTag', ], }, subject: { @@ -1503,6 +1537,22 @@ export const schemaDict = { description: 'If specified, only events where all of these labels were removed are returned', }, + addedTags: { + type: 'array', + items: { + type: 'string', + }, + description: + 'If specified, only events where all of these tags were added are returned', + }, + removedTags: { + type: 'array', + items: { + type: 'string', + }, + description: + 'If specified, only events where all of these tags were removed are returned', + }, reportTypes: { type: 'array', items: { @@ -1620,6 +1670,18 @@ export const schemaDict = { maximum: 100, default: 50, }, + tags: { + type: 'array', + items: { + type: 'string', + }, + }, + excludeTags: { + type: 'array', + items: { + type: 'string', + }, + }, cursor: { type: 'string', }, diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/defs.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/defs.ts index 2fab0bc19a4..a713a635635 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/defs.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/defs.ts @@ -156,6 +156,7 @@ export interface SubjectStatusView { /** True indicates that the a previously taken moderator action was appealed against, by the author of the content. False indicates last appeal was resolved by moderators. */ appealed?: boolean suspendUntil?: string + tags?: string[] [k: string]: unknown } @@ -720,6 +721,29 @@ export function validateModEventEmail(v: unknown): ValidationResult { return lexicons.validate('com.atproto.admin.defs#modEventEmail', v) } +/** Add/Remove a tag on a subject */ +export interface ModEventTag { + /** Tags to be added to the subject. If already exists, won't be duplicated. */ + add: string[] + /** Tags to be removed to the subject. Ignores a tag If it doesn't exist, won't be duplicated. */ + remove: string[] + /** Additional comment about added/removed tags. */ + comment?: string + [k: string]: unknown +} + +export function isModEventTag(v: unknown): v is ModEventTag { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'com.atproto.admin.defs#modEventTag' + ) +} + +export function validateModEventTag(v: unknown): ValidationResult { + return lexicons.validate('com.atproto.admin.defs#modEventTag', v) +} + export interface CommunicationTemplateView { id: string /** Name of the template. */ diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts index 3c0acabd0eb..99d08c7f1b7 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts @@ -24,6 +24,7 @@ export interface InputSchema { | ComAtprotoAdminDefs.ModEventReverseTakedown | ComAtprotoAdminDefs.ModEventUnmute | ComAtprotoAdminDefs.ModEventEmail + | ComAtprotoAdminDefs.ModEventTag | { $type: string; [k: string]: unknown } subject: | ComAtprotoAdminDefs.RepoRef diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts index df07cfaf2da..9f4738578aa 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts @@ -31,6 +31,10 @@ export interface QueryParams { addedLabels?: string[] /** If specified, only events where all of these labels were removed are returned */ removedLabels?: string[] + /** If specified, only events where all of these tags were added are returned */ + addedTags?: string[] + /** If specified, only events where all of these tags were removed are returned */ + removedTags?: string[] reportTypes?: string[] cursor?: string } diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts index 0cc21c25352..f5031d25117 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts @@ -35,6 +35,8 @@ export interface QueryParams { /** Get subjects in unresolved appealed status */ appealed?: boolean limit: number + tags?: string[] + excludeTags?: string[] cursor?: string } diff --git a/packages/bsky/tests/auto-moderator/labeler.test.ts b/packages/bsky/tests/auto-moderator/labeler.test.ts index e0414a0d627..5962a3d374c 100644 --- a/packages/bsky/tests/auto-moderator/labeler.test.ts +++ b/packages/bsky/tests/auto-moderator/labeler.test.ts @@ -105,6 +105,8 @@ describe('labeler', () => { types: [], addedLabels: [], removedLabels: [], + addedTags: [], + removedTags: [], }) expect(events.length).toBe(1) expect(events[0]).toMatchObject({ diff --git a/packages/ozone/package.json b/packages/ozone/package.json index 1e91ca18bbf..d0183c1061c 100644 --- a/packages/ozone/package.json +++ b/packages/ozone/package.json @@ -34,9 +34,9 @@ "@atproto/api": "workspace:^", "@atproto/common": "workspace:^", "@atproto/crypto": "workspace:^", - "@atproto/syntax": "workspace:^", "@atproto/identity": "workspace:^", "@atproto/lexicon": "workspace:^", + "@atproto/syntax": "workspace:^", "@atproto/xrpc-server": "workspace:^", "@did-plc/lib": "^0.0.1", "compression": "^1.7.4", diff --git a/packages/ozone/src/api/admin/emitModerationEvent.ts b/packages/ozone/src/api/admin/emitModerationEvent.ts index bc198398013..c04dfbecae6 100644 --- a/packages/ozone/src/api/admin/emitModerationEvent.ts +++ b/packages/ozone/src/api/admin/emitModerationEvent.ts @@ -7,6 +7,7 @@ import { isModEventTakedown, } from '../../lexicon/types/com/atproto/admin/defs' import { subjectFromInput } from '../../mod-service/subject' +import { ModerationLangService } from '../../mod-service/lang' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.emitModerationEvent({ @@ -77,10 +78,21 @@ export default function (server: Server, ctx: AppContext) { createdBy, }) + const moderationLangService = new ModerationLangService(moderationTxn) + await moderationLangService.tagSubjectWithLang({ + subject, + createdBy: ctx.cfg.service.did, + subjectStatus: result.subjectStatus, + }) + if (subject.isRepo()) { if (isTakedownEvent) { - const isSuspend = !!result.durationInHours - await moderationTxn.takedownRepo(subject, result.id, isSuspend) + const isSuspend = !!result.event.durationInHours + await moderationTxn.takedownRepo( + subject, + result.event.id, + isSuspend, + ) } else if (isReverseTakedownEvent) { await moderationTxn.reverseTakedownRepo(subject) } @@ -88,7 +100,7 @@ export default function (server: Server, ctx: AppContext) { if (subject.isRecord()) { if (isTakedownEvent) { - await moderationTxn.takedownRecord(subject, result.id) + await moderationTxn.takedownRecord(subject, result.event.id) } else if (isReverseTakedownEvent) { await moderationTxn.reverseTakedownRecord(subject) } @@ -96,20 +108,20 @@ export default function (server: Server, ctx: AppContext) { if (isLabelEvent) { await moderationTxn.formatAndCreateLabels( - result.subjectUri ?? result.subjectDid, - result.subjectCid, + result.event.subjectUri ?? result.event.subjectDid, + result.event.subjectCid, { - create: result.createLabelVals?.length - ? result.createLabelVals.split(' ') + create: result.event.createLabelVals?.length + ? result.event.createLabelVals.split(' ') : undefined, - negate: result.negateLabelVals?.length - ? result.negateLabelVals.split(' ') + negate: result.event.negateLabelVals?.length + ? result.event.negateLabelVals.split(' ') : undefined, }, ) } - return result + return result.event }) return { diff --git a/packages/ozone/src/api/admin/queryModerationEvents.ts b/packages/ozone/src/api/admin/queryModerationEvents.ts index b2ea4df7323..670cda96cbc 100644 --- a/packages/ozone/src/api/admin/queryModerationEvents.ts +++ b/packages/ozone/src/api/admin/queryModerationEvents.ts @@ -20,6 +20,8 @@ export default function (server: Server, ctx: AppContext) { createdBefore, addedLabels = [], removedLabels = [], + addedTags = [], + removedTags = [], reportTypes, } = params const db = ctx.db @@ -37,7 +39,9 @@ export default function (server: Server, ctx: AppContext) { createdAfter, createdBefore, addedLabels, + addedTags, removedLabels, + removedTags, reportTypes, }) return { diff --git a/packages/ozone/src/api/admin/queryModerationStatuses.ts b/packages/ozone/src/api/admin/queryModerationStatuses.ts index fc935e5917a..fc491339ffa 100644 --- a/packages/ozone/src/api/admin/queryModerationStatuses.ts +++ b/packages/ozone/src/api/admin/queryModerationStatuses.ts @@ -22,6 +22,8 @@ export default function (server: Server, ctx: AppContext) { includeMuted = false, limit = 50, cursor, + tags = [], + excludeTags = [], } = params const db = ctx.db const modService = ctx.modService(db) @@ -41,6 +43,8 @@ export default function (server: Server, ctx: AppContext) { sortField, limit, cursor, + tags, + excludeTags, }) const subjectStatuses = results.statuses.map((status) => modService.views.formatSubjectStatus(status), diff --git a/packages/ozone/src/api/moderation/createReport.ts b/packages/ozone/src/api/moderation/createReport.ts index 6ede6dcd0e4..e87b957e8d0 100644 --- a/packages/ozone/src/api/moderation/createReport.ts +++ b/packages/ozone/src/api/moderation/createReport.ts @@ -4,6 +4,7 @@ import { getReasonType } from './util' import { subjectFromInput } from '../../mod-service/subject' import { REASONAPPEAL } from '../../lexicon/types/com/atproto/moderation/defs' import { ForbiddenError } from '@atproto/xrpc-server' +import { ModerationLangService } from '../../mod-service/lang' export default function (server: Server, ctx: AppContext) { server.com.atproto.moderation.createReport({ @@ -23,12 +24,22 @@ export default function (server: Server, ctx: AppContext) { const db = ctx.db const report = await db.transaction(async (dbTxn) => { const moderationTxn = ctx.modService(dbTxn) - return moderationTxn.report({ - reasonType: getReasonType(reasonType), - reason, + const { event: reportEvent, subjectStatus } = + await moderationTxn.report({ + reasonType: getReasonType(reasonType), + reason, + subject, + reportedBy: requester || ctx.cfg.service.did, + }) + + const moderationLangService = new ModerationLangService(moderationTxn) + await moderationLangService.tagSubjectWithLang({ subject, - reportedBy: requester || ctx.cfg.service.did, + subjectStatus, + createdBy: ctx.cfg.service.did, }) + + return reportEvent }) const body = ctx.modService(db).views.formatReport(report) diff --git a/packages/ozone/src/api/moderation/util.ts b/packages/ozone/src/api/moderation/util.ts index f78829240d0..e64229891a5 100644 --- a/packages/ozone/src/api/moderation/util.ts +++ b/packages/ozone/src/api/moderation/util.ts @@ -63,4 +63,5 @@ const eventTypes = new Set([ 'com.atproto.admin.defs#modEventReverseTakedown', 'com.atproto.admin.defs#modEventEmail', 'com.atproto.admin.defs#modEventResolveAppeal', + 'com.atproto.admin.defs#modEventTag', ]) diff --git a/packages/ozone/src/db/migrations/20240208T213404429Z-add-tags-column-to-moderation-subject.ts b/packages/ozone/src/db/migrations/20240208T213404429Z-add-tags-column-to-moderation-subject.ts new file mode 100644 index 00000000000..0c323ace91c --- /dev/null +++ b/packages/ozone/src/db/migrations/20240208T213404429Z-add-tags-column-to-moderation-subject.ts @@ -0,0 +1,31 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .alterTable('moderation_event') + .addColumn('addedTags', 'jsonb') + .execute() + await db.schema + .alterTable('moderation_event') + .addColumn('removedTags', 'jsonb') + .execute() + await db.schema + .alterTable('moderation_subject_status') + .addColumn('tags', 'jsonb') + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema + .alterTable('moderation_event') + .dropColumn('addedTags') + .execute() + await db.schema + .alterTable('moderation_event') + .dropColumn('removedTags') + .execute() + await db.schema + .alterTable('moderation_subject_status') + .dropColumn('tags') + .execute() +} diff --git a/packages/ozone/src/db/migrations/index.ts b/packages/ozone/src/db/migrations/index.ts index 7560c5bf482..1a823f860c5 100644 --- a/packages/ozone/src/db/migrations/index.ts +++ b/packages/ozone/src/db/migrations/index.ts @@ -5,3 +5,4 @@ export * as _20231219T205730722Z from './20231219T205730722Z-init' export * as _20240116T085607200Z from './20240116T085607200Z-communication-template' export * as _20240201T051104136Z from './20240201T051104136Z-mod-event-blobs' +export * as _20240208T213404429Z from './20240208T213404429Z-add-tags-column-to-moderation-subject' diff --git a/packages/ozone/src/db/schema/moderation_event.ts b/packages/ozone/src/db/schema/moderation_event.ts index 1692a3546fb..50bfb5edccf 100644 --- a/packages/ozone/src/db/schema/moderation_event.ts +++ b/packages/ozone/src/db/schema/moderation_event.ts @@ -15,6 +15,7 @@ export interface ModerationEvent { | 'com.atproto.admin.defs#modEventReverseTakedown' | 'com.atproto.admin.defs#modEventEmail' | 'com.atproto.admin.defs#modEventResolveAppeal' + | 'com.atproto.admin.defs#modEventTag' subjectType: 'com.atproto.admin.defs#repoRef' | 'com.atproto.repo.strongRef' subjectDid: string subjectUri: string | null @@ -28,6 +29,8 @@ export interface ModerationEvent { durationInHours: number | null expiresAt: string | null meta: Record | null + addedTags: string[] | null + removedTags: string[] | null legacyRefId: number | null } diff --git a/packages/ozone/src/db/schema/moderation_subject_status.ts b/packages/ozone/src/db/schema/moderation_subject_status.ts index 6e67082f31c..59803133bcb 100644 --- a/packages/ozone/src/db/schema/moderation_subject_status.ts +++ b/packages/ozone/src/db/schema/moderation_subject_status.ts @@ -25,6 +25,7 @@ export interface ModerationSubjectStatus { takendown: boolean appealed: boolean | null comment: string | null + tags: string[] | null } export type PartialDB = { diff --git a/packages/ozone/src/lexicon/lexicons.ts b/packages/ozone/src/lexicon/lexicons.ts index a13f5fbf1f2..01a2f683d77 100644 --- a/packages/ozone/src/lexicon/lexicons.ts +++ b/packages/ozone/src/lexicon/lexicons.ts @@ -303,6 +303,12 @@ export const schemaDict = { type: 'string', format: 'datetime', }, + tags: { + type: 'array', + items: { + type: 'string', + }, + }, }, }, reportViewDetail: { @@ -897,6 +903,33 @@ export const schemaDict = { }, }, }, + modEventTag: { + type: 'object', + description: 'Add/Remove a tag on a subject', + required: ['add', 'remove'], + properties: { + add: { + type: 'array', + items: { + type: 'string', + }, + description: + "Tags to be added to the subject. If already exists, won't be duplicated.", + }, + remove: { + type: 'array', + items: { + type: 'string', + }, + description: + "Tags to be removed to the subject. Ignores a tag If it doesn't exist, won't be duplicated.", + }, + comment: { + type: 'string', + description: 'Additional comment about added/removed tags.', + }, + }, + }, communicationTemplateView: { type: 'object', required: [ @@ -1075,6 +1108,7 @@ export const schemaDict = { 'lex:com.atproto.admin.defs#modEventReverseTakedown', 'lex:com.atproto.admin.defs#modEventUnmute', 'lex:com.atproto.admin.defs#modEventEmail', + 'lex:com.atproto.admin.defs#modEventTag', ], }, subject: { @@ -1503,6 +1537,22 @@ export const schemaDict = { description: 'If specified, only events where all of these labels were removed are returned', }, + addedTags: { + type: 'array', + items: { + type: 'string', + }, + description: + 'If specified, only events where all of these tags were added are returned', + }, + removedTags: { + type: 'array', + items: { + type: 'string', + }, + description: + 'If specified, only events where all of these tags were removed are returned', + }, reportTypes: { type: 'array', items: { @@ -1620,6 +1670,18 @@ export const schemaDict = { maximum: 100, default: 50, }, + tags: { + type: 'array', + items: { + type: 'string', + }, + }, + excludeTags: { + type: 'array', + items: { + type: 'string', + }, + }, cursor: { type: 'string', }, diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/defs.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/defs.ts index 2fab0bc19a4..a713a635635 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/defs.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/defs.ts @@ -156,6 +156,7 @@ export interface SubjectStatusView { /** True indicates that the a previously taken moderator action was appealed against, by the author of the content. False indicates last appeal was resolved by moderators. */ appealed?: boolean suspendUntil?: string + tags?: string[] [k: string]: unknown } @@ -720,6 +721,29 @@ export function validateModEventEmail(v: unknown): ValidationResult { return lexicons.validate('com.atproto.admin.defs#modEventEmail', v) } +/** Add/Remove a tag on a subject */ +export interface ModEventTag { + /** Tags to be added to the subject. If already exists, won't be duplicated. */ + add: string[] + /** Tags to be removed to the subject. Ignores a tag If it doesn't exist, won't be duplicated. */ + remove: string[] + /** Additional comment about added/removed tags. */ + comment?: string + [k: string]: unknown +} + +export function isModEventTag(v: unknown): v is ModEventTag { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'com.atproto.admin.defs#modEventTag' + ) +} + +export function validateModEventTag(v: unknown): ValidationResult { + return lexicons.validate('com.atproto.admin.defs#modEventTag', v) +} + export interface CommunicationTemplateView { id: string /** Name of the template. */ diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts index 3c0acabd0eb..99d08c7f1b7 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts @@ -24,6 +24,7 @@ export interface InputSchema { | ComAtprotoAdminDefs.ModEventReverseTakedown | ComAtprotoAdminDefs.ModEventUnmute | ComAtprotoAdminDefs.ModEventEmail + | ComAtprotoAdminDefs.ModEventTag | { $type: string; [k: string]: unknown } subject: | ComAtprotoAdminDefs.RepoRef diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts index df07cfaf2da..9f4738578aa 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts @@ -31,6 +31,10 @@ export interface QueryParams { addedLabels?: string[] /** If specified, only events where all of these labels were removed are returned */ removedLabels?: string[] + /** If specified, only events where all of these tags were added are returned */ + addedTags?: string[] + /** If specified, only events where all of these tags were removed are returned */ + removedTags?: string[] reportTypes?: string[] cursor?: string } diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts index 0cc21c25352..f5031d25117 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts @@ -35,6 +35,8 @@ export interface QueryParams { /** Get subjects in unresolved appealed status */ appealed?: boolean limit: number + tags?: string[] + excludeTags?: string[] cursor?: string } diff --git a/packages/ozone/src/logger.ts b/packages/ozone/src/logger.ts index 42b0a78b2a4..0b3e5acf52c 100644 --- a/packages/ozone/src/logger.ts +++ b/packages/ozone/src/logger.ts @@ -5,6 +5,8 @@ export const dbLogger: ReturnType = subsystemLogger('ozone:db') export const httpLogger: ReturnType = subsystemLogger('ozone') +export const langLogger: ReturnType = + subsystemLogger('ozone:lang') export const loggerMiddleware = pinoHttp({ logger: httpLogger, diff --git a/packages/ozone/src/mod-service/index.ts b/packages/ozone/src/mod-service/index.ts index f617301742b..7f06272d55d 100644 --- a/packages/ozone/src/mod-service/index.ts +++ b/packages/ozone/src/mod-service/index.ts @@ -12,6 +12,7 @@ import { isModEventReport, isModEventTakedown, isModEventEmail, + isModEventTag, RepoRef, RepoBlobRef, } from '../lexicon/types/com/atproto/admin/defs' @@ -103,6 +104,8 @@ export class ModerationService { createdBefore?: string addedLabels: string[] removedLabels: string[] + addedTags: string[] + removedTags: string[] reportTypes?: string[] }): Promise<{ cursor?: string; events: ModerationEventRow[] }> { const { @@ -119,8 +122,11 @@ export class ModerationService { createdBefore, addedLabels, removedLabels, + addedTags, + removedTags, reportTypes, } = opts + const { ref } = this.db.db.dynamic let builder = this.db.db.selectFrom('moderation_event').selectAll() if (subject) { builder = builder.where((qb) => { @@ -178,11 +184,18 @@ export class ModerationService { builder = builder.where('negateLabelVals', 'ilike', `%${label}%`) }) } + if (addedTags.length) { + builder = builder.where(sql`${ref('addedTags')} @> ${jsonb(addedTags)}`) + } + if (removedTags.length) { + builder = builder.where( + sql`${ref('removedTags')} @> ${jsonb(removedTags)}`, + ) + } if (reportTypes?.length) { builder = builder.where(sql`meta->>'reportType'`, 'in', reportTypes) } - const { ref } = this.db.db.dynamic const keyset = new TimeIdKeyset( ref(`moderation_event.createdAt`), ref('moderation_event.id'), @@ -238,7 +251,10 @@ export class ModerationService { subject: ModSubject createdBy: string createdAt?: Date - }): Promise { + }): Promise<{ + event: ModerationEventRow + subjectStatus: ModerationSubjectStatusRow | null + }> { this.db.assertTransaction() const { event, subject, createdBy, createdAt = new Date() } = info @@ -253,6 +269,9 @@ export class ModerationService { const meta: Record = {} + const addedTags = isModEventTag(event) ? jsonb(event.add) : null + const removedTags = isModEventTag(event) ? jsonb(event.remove) : null + if (isModEventReport(event)) { meta.reportType = event.reportType } @@ -276,6 +295,8 @@ export class ModerationService { createdBy, createLabelVals, negateLabelVals, + addedTags, + removedTags, durationInHours: event.durationInHours ? Number(event.durationInHours) : null, @@ -294,9 +315,13 @@ export class ModerationService { .returningAll() .executeTakeFirstOrThrow() - await adjustModerationSubjectStatus(this.db, modEvent, subject.blobCids) + const subjectStatus = await adjustModerationSubjectStatus( + this.db, + modEvent, + subject.blobCids, + ) - return modEvent + return { event: modEvent, subjectStatus } } async getLastReversibleEventForSubject(subject: ReversalSubject) { @@ -376,7 +401,7 @@ export class ModerationService { const isRevertingTakedown = action === 'com.atproto.admin.defs#modEventTakedown' this.db.assertTransaction() - const result = await this.logEvent({ + const { event } = await this.logEvent({ event: { $type: isRevertingTakedown ? 'com.atproto.admin.defs#modEventReverseTakedown' @@ -396,7 +421,7 @@ export class ModerationService { } } - return result + return event } async takedownRepo( @@ -613,7 +638,10 @@ export class ModerationService { subject: ModSubject reportedBy: string createdAt?: Date - }): Promise { + }): Promise<{ + event: ModerationEventRow + subjectStatus: ModerationSubjectStatusRow | null + }> { const { reasonType, reason, @@ -622,7 +650,7 @@ export class ModerationService { subject, } = info - const event = await this.logEvent({ + const result = await this.logEvent({ event: { $type: 'com.atproto.admin.defs#modEventReport', reportType: reasonType, @@ -633,7 +661,7 @@ export class ModerationService { createdAt, }) - return event + return result } async getSubjectStatuses({ @@ -652,6 +680,8 @@ export class ModerationService { lastReviewedBy, sortField, subject, + tags, + excludeTags, }: { cursor?: string limit?: number @@ -668,8 +698,11 @@ export class ModerationService { sortDirection: 'asc' | 'desc' lastReviewedBy?: string sortField: 'lastReviewedAt' | 'lastReportedAt' + tags: string[] + excludeTags: string[] }) { let builder = this.db.db.selectFrom('moderation_subject_status').selectAll() + const { ref } = this.db.db.dynamic if (subject) { const subjectInfo = getStatusIdentifierFromSubject(subject) @@ -731,7 +764,24 @@ export class ModerationService { ) } - const { ref } = this.db.db.dynamic + if (tags.length) { + builder = builder.where( + sql`${ref('moderation_subject_status.tags')} @> ${jsonb(tags)}`, + ) + } + + if (excludeTags.length) { + builder = builder.where((qb) => + qb + .where( + sql`NOT(${ref('moderation_subject_status.tags')} @> ${jsonb( + excludeTags, + )})`, + ) + .orWhere('tags', 'is', null), + ) + } + const keyset = new StatusKeyset( ref(`moderation_subject_status.${sortField}`), ref('moderation_subject_status.id'), diff --git a/packages/ozone/src/mod-service/lang.ts b/packages/ozone/src/mod-service/lang.ts new file mode 100644 index 00000000000..91d3d12d5ce --- /dev/null +++ b/packages/ozone/src/mod-service/lang.ts @@ -0,0 +1,82 @@ +import { ModerationService } from '.' +import { ModSubject } from './subject' +import { ModerationSubjectStatusRow } from './types' +import { langLogger as log } from '../logger' + +export class ModerationLangService { + constructor(private moderationService: ModerationService) {} + + async tagSubjectWithLang({ + subject, + subjectStatus, + createdBy, + }: { + subject: ModSubject + createdBy: string + subjectStatus: ModerationSubjectStatusRow | null + }) { + if ( + subjectStatus && + !subjectStatus.tags?.find((tag) => tag.includes('lang:')) + ) { + try { + const recordLangs = await this.getRecordLang({ + subject, + }) + await this.moderationService.logEvent({ + event: { + $type: 'com.atproto.admin.defs#modEventTag', + add: recordLangs + ? recordLangs.map((lang) => `lang:${lang}`) + : ['lang:und'], + remove: [], + }, + subject, + createdBy, + }) + } catch (err) { + log.error({ subject, err }, 'Error getting record langs') + } + } + } + + async getRecordLang({ + subject, + }: { + subject: ModSubject + }): Promise { + const isRecord = subject.isRecord() + const langs = new Set() + + if ( + subject.isRepo() || + (isRecord && subject.uri.endsWith('/app.bsky.actor.profile/self')) + ) { + const feed = await this.moderationService.views.fetchAuthorFeed( + subject.did, + ) + feed.forEach((item) => { + const itemLangs = item.post.record['langs'] as string[] | null + if (itemLangs?.length) { + // Pick the first fragment of the lang code so that instead of `en-US` and `en-GB` we get `en` + itemLangs.forEach((lang) => langs.add(lang.split('-')[0])) + } + }) + } + + if (isRecord) { + const recordByUri = await this.moderationService.views.fetchRecords([ + subject, + ]) + const record = recordByUri.get(subject.uri) + const recordLang = record?.value.langs as string[] | null + if (recordLang?.length) { + recordLang + .map((lang) => lang.split('-')[0]) + .forEach((lang) => langs.add(lang)) + } + } + + return langs.size > 0 ? Array.from(langs) : null + } +} diff --git a/packages/ozone/src/mod-service/status.ts b/packages/ozone/src/mod-service/status.ts index 359228a313d..2edba64282f 100644 --- a/packages/ozone/src/mod-service/status.ts +++ b/packages/ozone/src/mod-service/status.ts @@ -10,7 +10,6 @@ import { } from '../lexicon/types/com/atproto/admin/defs' import { ModerationEventRow, ModerationSubjectStatusRow } from './types' import { HOUR } from '@atproto/common' -import { sql } from 'kysely' import { REASONAPPEAL } from '../lexicon/types/com/atproto/moderation/defs' import { jsonb } from '../db/types' @@ -83,6 +82,8 @@ const getSubjectStatusForModerationEvent = ({ lastReviewedBy: createdBy, lastReviewedAt: createdAt, } + case 'com.atproto.admin.defs#modEventTag': + return { tags: [] } case 'com.atproto.admin.defs#modEventResolveAppeal': return { appealed: false, @@ -107,6 +108,8 @@ export const adjustModerationSubjectStatus = async ( subjectCid, createdBy, meta, + addedTags, + removedTags, comment, createdAt, } = moderationEvent @@ -191,6 +194,18 @@ export const adjustModerationSubjectStatus = async ( subjectStatus.comment = comment } + if (action === 'com.atproto.admin.defs#modEventTag') { + let tags = currentStatus?.tags || [] + if (addedTags?.length) { + tags = tags.concat(addedTags) + } + if (removedTags?.length) { + tags = tags.filter((tag) => !removedTags.includes(tag)) + } + newStatus.tags = jsonb([...new Set(tags)]) as unknown as string[] + subjectStatus.tags = newStatus.tags + } + if (blobCids?.length) { const newBlobCids = jsonb( blobCids, @@ -206,7 +221,6 @@ export const adjustModerationSubjectStatus = async ( ...newStatus, createdAt: now, updatedAt: now, - // TODO: Need to get the types right here. } as ModerationSubjectStatusRow) .onConflict((oc) => oc.constraint('moderation_status_unique_idx').doUpdateSet({ @@ -215,8 +229,8 @@ export const adjustModerationSubjectStatus = async ( }), ) - const status = await insertQuery.executeTakeFirst() - return status + const status = await insertQuery.returningAll().executeTakeFirst() + return status || null } type ModerationSubjectStatusFilter = diff --git a/packages/ozone/src/mod-service/types.ts b/packages/ozone/src/mod-service/types.ts index aca5d99c4ed..6099384a3cd 100644 --- a/packages/ozone/src/mod-service/types.ts +++ b/packages/ozone/src/mod-service/types.ts @@ -30,6 +30,7 @@ export type ModEventType = | ComAtprotoAdminDefs.ModEventReport | ComAtprotoAdminDefs.ModEventMute | ComAtprotoAdminDefs.ModEventReverseTakedown + | ComAtprotoAdminDefs.ModEventTag export const UNSPECCED_TAKEDOWN_LABEL = '!unspecced-takedown' diff --git a/packages/ozone/src/mod-service/views.ts b/packages/ozone/src/mod-service/views.ts index 4c15a00218d..e21d2a46f5c 100644 --- a/packages/ozone/src/mod-service/views.ts +++ b/packages/ozone/src/mod-service/views.ts @@ -1,6 +1,6 @@ import { sql } from 'kysely' import { AtUri, INVALID_HANDLE, normalizeDatetimeAlways } from '@atproto/syntax' -import AtpAgent from '@atproto/api' +import AtpAgent, { AppBskyFeedDefs } from '@atproto/api' import { dedupeStrs } from '@atproto/common' import { BlobRef } from '@atproto/lexicon' import { Database } from '../db' @@ -163,6 +163,11 @@ export class ModerationViews { eventView.event.sticky = true } + if (event.action === 'com.atproto.admin.defs#modEventTag') { + eventView.event.add = event.addedTags || [] + eventView.event.remove = event.removedTags || [] + } + return eventView } @@ -217,7 +222,7 @@ export class ModerationViews { subjects.map(async (subject) => { const uri = new AtUri(subject.uri) try { - return await this.appviewAgent.api.com.atproto.repo.getRecord( + const record = await this.appviewAgent.api.com.atproto.repo.getRecord( { repo: uri.hostname, collection: uri.collection, @@ -226,6 +231,7 @@ export class ModerationViews { }, auth, ) + return record } catch { return null } @@ -473,9 +479,22 @@ export class ModerationViews { appealed: status.appealed ?? undefined, subjectRepoHandle: status.handle ?? undefined, subjectBlobCids: status.blobCids || [], + tags: status.tags || [], subject: subjectFromStatusRow(status).lex(), } } + + async fetchAuthorFeed( + actor: string, + ): Promise { + const auth = await this.appviewAuth() + if (!auth) return [] + const { + data: { feed }, + } = await this.appviewAgent.api.app.bsky.feed.getAuthorFeed({ actor }, auth) + + return feed + } } type RecordSubject = { uri: string; cid?: string } diff --git a/packages/ozone/tests/__snapshots__/get-record.test.ts.snap b/packages/ozone/tests/__snapshots__/get-record.test.ts.snap index 1ccaed3b85d..decfb8f4ba4 100644 --- a/packages/ozone/tests/__snapshots__/get-record.test.ts.snap +++ b/packages/ozone/tests/__snapshots__/get-record.test.ts.snap @@ -39,6 +39,9 @@ Object { }, "subjectBlobCids": Array [], "subjectRepoHandle": "alice.test", + "tags": Array [ + "lang:und", + ], "takendown": true, "updatedAt": "1970-01-01T00:00:00.000Z", }, @@ -133,6 +136,9 @@ Object { }, "subjectBlobCids": Array [], "subjectRepoHandle": "alice.test", + "tags": Array [ + "lang:und", + ], "takendown": true, "updatedAt": "1970-01-01T00:00:00.000Z", }, diff --git a/packages/ozone/tests/__snapshots__/get-repo.test.ts.snap b/packages/ozone/tests/__snapshots__/get-repo.test.ts.snap index 1e51ee541ed..67404b88362 100644 --- a/packages/ozone/tests/__snapshots__/get-repo.test.ts.snap +++ b/packages/ozone/tests/__snapshots__/get-repo.test.ts.snap @@ -31,6 +31,9 @@ Object { }, "subjectBlobCids": Array [], "subjectRepoHandle": "alice.test", + "tags": Array [ + "lang:und", + ], "takendown": true, "updatedAt": "1970-01-01T00:00:00.000Z", }, diff --git a/packages/ozone/tests/__snapshots__/moderation-events.test.ts.snap b/packages/ozone/tests/__snapshots__/moderation-events.test.ts.snap index 0ebda08b84e..ac48d862f58 100644 --- a/packages/ozone/tests/__snapshots__/moderation-events.test.ts.snap +++ b/packages/ozone/tests/__snapshots__/moderation-events.test.ts.snap @@ -37,6 +37,9 @@ Object { }, "subjectBlobCids": Array [], "subjectRepoHandle": "alice.test", + "tags": Array [ + "lang:und", + ], "takendown": false, "updatedAt": "1970-01-01T00:00:00.000Z", }, @@ -98,7 +101,26 @@ Array [ "comment": "X", "reportType": "com.atproto.moderation.defs#reasonSpam", }, - "id": 9, + "id": 13, + "subject": Object { + "$type": "com.atproto.admin.defs#repoRef", + "did": "user(0)", + }, + "subjectBlobCids": Array [], + "subjectHandle": "bob.test", + }, + Object { + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "user(2)", + "event": Object { + "$type": "com.atproto.admin.defs#modEventTag", + "add": Array [ + "lang:en", + "lang:i", + ], + "remove": Array [], + }, + "id": 8, "subject": Object { "$type": "com.atproto.admin.defs#repoRef", "did": "user(0)", @@ -115,7 +137,7 @@ Array [ "comment": "X", "reportType": "com.atproto.moderation.defs#reasonSpam", }, - "id": 5, + "id": 7, "subject": Object { "$type": "com.atproto.admin.defs#repoRef", "did": "user(0)", @@ -137,7 +159,26 @@ Array [ "comment": "X", "reportType": "com.atproto.moderation.defs#reasonSpam", }, - "id": 8, + "id": 12, + "subject": Object { + "$type": "com.atproto.repo.strongRef", + "cid": "cids(0)", + "uri": "record(0)", + }, + "subjectBlobCids": Array [], + "subjectHandle": "alice.test", + }, + Object { + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "user(1)", + "event": Object { + "$type": "com.atproto.admin.defs#modEventTag", + "add": Array [ + "lang:und", + ], + "remove": Array [], + }, + "id": 6, "subject": Object { "$type": "com.atproto.repo.strongRef", "cid": "cids(0)", @@ -155,7 +196,7 @@ Array [ "comment": "X", "reportType": "com.atproto.moderation.defs#reasonSpam", }, - "id": 4, + "id": 5, "subject": Object { "$type": "com.atproto.repo.strongRef", "cid": "cids(0)", diff --git a/packages/ozone/tests/__snapshots__/moderation-statuses.test.ts.snap b/packages/ozone/tests/__snapshots__/moderation-statuses.test.ts.snap index a4939733d1a..cf361739a6c 100644 --- a/packages/ozone/tests/__snapshots__/moderation-statuses.test.ts.snap +++ b/packages/ozone/tests/__snapshots__/moderation-statuses.test.ts.snap @@ -1,10 +1,52 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`moderation-statuses query statuses returns statuses filtered by subject language 1`] = ` +Array [ + Object { + "createdAt": "1970-01-01T00:00:00.000Z", + "id": 7, + "lastReportedAt": "1970-01-01T00:00:00.000Z", + "reviewState": "com.atproto.admin.defs#reviewOpen", + "subject": Object { + "$type": "com.atproto.repo.strongRef", + "cid": "cids(0)", + "uri": "record(0)", + }, + "subjectBlobCids": Array [], + "subjectRepoHandle": "bob.test", + "tags": Array [ + "lang:en", + "lang:i", + ], + "takendown": false, + "updatedAt": "1970-01-01T00:00:00.000Z", + }, + Object { + "createdAt": "1970-01-01T00:00:00.000Z", + "id": 5, + "lastReportedAt": "1970-01-01T00:00:00.000Z", + "reviewState": "com.atproto.admin.defs#reviewOpen", + "subject": Object { + "$type": "com.atproto.admin.defs#repoRef", + "did": "user(0)", + }, + "subjectBlobCids": Array [], + "subjectRepoHandle": "bob.test", + "tags": Array [ + "lang:en", + "lang:i", + ], + "takendown": false, + "updatedAt": "1970-01-01T00:00:00.000Z", + }, +] +`; + exports[`moderation-statuses query statuses returns statuses for subjects that received moderation events 1`] = ` Array [ Object { "createdAt": "1970-01-01T00:00:00.000Z", - "id": 4, + "id": 7, "lastReportedAt": "1970-01-01T00:00:00.000Z", "reviewState": "com.atproto.admin.defs#reviewOpen", "subject": Object { @@ -14,12 +56,16 @@ Array [ }, "subjectBlobCids": Array [], "subjectRepoHandle": "bob.test", + "tags": Array [ + "lang:en", + "lang:i", + ], "takendown": false, "updatedAt": "1970-01-01T00:00:00.000Z", }, Object { "createdAt": "1970-01-01T00:00:00.000Z", - "id": 3, + "id": 5, "lastReportedAt": "1970-01-01T00:00:00.000Z", "reviewState": "com.atproto.admin.defs#reviewOpen", "subject": Object { @@ -28,12 +74,16 @@ Array [ }, "subjectBlobCids": Array [], "subjectRepoHandle": "bob.test", + "tags": Array [ + "lang:en", + "lang:i", + ], "takendown": false, "updatedAt": "1970-01-01T00:00:00.000Z", }, Object { "createdAt": "1970-01-01T00:00:00.000Z", - "id": 2, + "id": 3, "lastReportedAt": "1970-01-01T00:00:00.000Z", "reviewState": "com.atproto.admin.defs#reviewOpen", "subject": Object { @@ -43,6 +93,9 @@ Array [ }, "subjectBlobCids": Array [], "subjectRepoHandle": "alice.test", + "tags": Array [ + "lang:und", + ], "takendown": false, "updatedAt": "1970-01-01T00:00:00.000Z", }, @@ -57,6 +110,9 @@ Array [ }, "subjectBlobCids": Array [], "subjectRepoHandle": "alice.test", + "tags": Array [ + "lang:und", + ], "takendown": false, "updatedAt": "1970-01-01T00:00:00.000Z", }, diff --git a/packages/ozone/tests/__snapshots__/moderation.test.ts.snap b/packages/ozone/tests/__snapshots__/moderation.test.ts.snap index d2f5731d42d..1cd4c192081 100644 --- a/packages/ozone/tests/__snapshots__/moderation.test.ts.snap +++ b/packages/ozone/tests/__snapshots__/moderation.test.ts.snap @@ -4,7 +4,7 @@ exports[`moderation reporting creates reports of a record. 1`] = ` Array [ Object { "createdAt": "1970-01-01T00:00:00.000Z", - "id": 6, + "id": 7, "reasonType": "com.atproto.moderation.defs#reasonSpam", "reportedBy": "user(0)", "subject": Object { @@ -15,7 +15,7 @@ Array [ }, Object { "createdAt": "1970-01-01T00:00:00.000Z", - "id": 7, + "id": 9, "reason": "defamation", "reasonType": "com.atproto.moderation.defs#reasonOther", "reportedBy": "user(1)", @@ -42,7 +42,7 @@ Array [ }, Object { "createdAt": "1970-01-01T00:00:00.000Z", - "id": 4, + "id": 5, "reason": "impersonation", "reasonType": "com.atproto.moderation.defs#reasonOther", "reportedBy": "user(2)", diff --git a/packages/ozone/tests/get-record.test.ts b/packages/ozone/tests/get-record.test.ts index 303e0f054d0..735f4725f11 100644 --- a/packages/ozone/tests/get-record.test.ts +++ b/packages/ozone/tests/get-record.test.ts @@ -27,14 +27,6 @@ describe('admin get record view', () => { }) beforeAll(async () => { - await sc.emitModerationEvent({ - event: { $type: 'com.atproto.admin.defs#modEventFlag' }, - subject: { - $type: 'com.atproto.repo.strongRef', - uri: sc.posts[sc.dids.alice][0].ref.uriStr, - cid: sc.posts[sc.dids.alice][0].ref.cidStr, - }, - }) await sc.createReport({ reportedBy: sc.dids.bob, reasonType: REASONSPAM, diff --git a/packages/ozone/tests/moderation-events.test.ts b/packages/ozone/tests/moderation-events.test.ts index 4d7b880f88e..12277ea77a4 100644 --- a/packages/ozone/tests/moderation-events.test.ts +++ b/packages/ozone/tests/moderation-events.test.ts @@ -148,7 +148,7 @@ describe('moderation-events', () => { expect( [...new Set(allEvents.data.events.map((e) => e.event.$type))].length, - ).toEqual(3) + ).toEqual(4) }) it('returns events for all content by user', async () => { @@ -203,10 +203,11 @@ describe('moderation-events', () => { const defaultEvents = await getPaginatedEvents() const reversedEvents = await getPaginatedEvents('asc') - expect(allEvents.data.events.length).toEqual(5) + expect(allEvents.data.events.length).toEqual(7) expect(defaultEvents.length).toEqual(allEvents.data.events.length) expect(reversedEvents.length).toEqual(allEvents.data.events.length) - expect(reversedEvents[0].id).toEqual(defaultEvents[4].id) + // First event in the reversed list is the last item in the default list + expect(reversedEvents[0].id).toEqual(defaultEvents[6].id) }) it('returns report events matching reportType filters', async () => { @@ -242,7 +243,7 @@ describe('moderation-events', () => { expect(eventsWithComment.data.events.length).toEqual(12) }) - it('returns events matching filter params for columns', async () => { + it('returns events matching filter params for labels', async () => { const [negatedLabelEvent, createdLabelEvent] = await Promise.all([ emitModerationEvent({ event: { @@ -301,6 +302,54 @@ describe('moderation-events', () => { withoutOneLabel.data.events[0].id, ) }) + it('returns events matching filter params for tags', async () => { + const tagEvent = async ({ + add, + remove, + }: { + add: string[] + remove: string[] + }) => + emitModerationEvent({ + event: { + $type: 'com.atproto.admin.defs#modEventTag', + comment: 'X', + add, + remove, + }, + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: sc.dids.carol, + }, + createdBy: sc.dids.bob, + }) + const addEvent = await tagEvent({ add: ['L1', 'L2'], remove: [] }) + const addAndRemoveEvent = await tagEvent({ add: ['L3'], remove: ['L2'] }) + const [addFinder, addAndRemoveFinder, removeFinder] = await Promise.all([ + queryModerationEvents({ + addedTags: ['L1'], + }), + queryModerationEvents({ + addedTags: ['L3'], + removedTags: ['L2'], + }), + queryModerationEvents({ + removedTags: ['L2'], + }), + ]) + + expect(addFinder.data.events.length).toEqual(1) + expect(addEvent.data.id).toEqual(addFinder.data.events[0].id) + + expect(addAndRemoveEvent.data.id).toEqual( + addAndRemoveFinder.data.events[0].id, + ) + expect(addAndRemoveEvent.data.id).toEqual( + addAndRemoveFinder.data.events[0].id, + ) + expect(addAndRemoveEvent.data.event.add).toEqual(['L3']) + expect(addAndRemoveEvent.data.event.remove).toEqual(['L2']) + }) }) describe('get event', () => { @@ -331,7 +380,10 @@ describe('moderation-events', () => { }) const { data: result } = await pdsAgent.api.com.atproto.admin.queryModerationEvents( - { subject: post.ref.uriStr }, + { + subject: post.ref.uriStr, + types: ['com.atproto.admin.defs#modEventTakedown'], + }, { headers: network.ozone.adminAuthHeaders('moderator') }, ) expect(result.events[0]).toMatchObject({ diff --git a/packages/ozone/tests/moderation-status-tags.test.ts b/packages/ozone/tests/moderation-status-tags.test.ts new file mode 100644 index 00000000000..eedac64ca39 --- /dev/null +++ b/packages/ozone/tests/moderation-status-tags.test.ts @@ -0,0 +1,92 @@ +import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' +import AtpAgent from '@atproto/api' +import { REASONSPAM } from '../src/lexicon/types/com/atproto/moderation/defs' + +describe('moderation-status-tags', () => { + let network: TestNetwork + let agent: AtpAgent + let pdsAgent: AtpAgent + let sc: SeedClient + + const emitModerationEvent = async (eventData) => { + return pdsAgent.api.com.atproto.admin.emitModerationEvent(eventData, { + encoding: 'application/json', + headers: network.bsky.adminAuthHeaders('moderator'), + }) + } + + const queryModerationStatuses = (statusQuery) => + agent.api.com.atproto.admin.queryModerationStatuses(statusQuery, { + headers: network.bsky.adminAuthHeaders('moderator'), + }) + + beforeAll(async () => { + network = await TestNetwork.create({ + dbPostgresSchema: 'ozone_moderation_status_tags', + }) + agent = network.ozone.getClient() + pdsAgent = network.pds.getClient() + sc = network.getSeedClient() + await basicSeed(sc) + await network.processAll() + }) + + afterAll(async () => { + await network.close() + }) + + describe('manage tags on subject status', () => { + it('adds and removes tags on a subject', async () => { + const bobsAccount = { + $type: 'com.atproto.admin.defs#repoRef', + did: sc.dids.bob, + } + await emitModerationEvent({ + subject: bobsAccount, + event: { + $type: 'com.atproto.admin.defs#modEventReport', + comment: 'X', + reportType: REASONSPAM, + }, + createdBy: sc.dids.alice, + }) + await emitModerationEvent({ + subject: bobsAccount, + event: { + $type: 'com.atproto.admin.defs#modEventTag', + add: ['interaction-churn'], + remove: [], + }, + createdBy: sc.dids.alice, + }) + const { data: statusAfterInteractionTag } = await queryModerationStatuses( + { + subject: bobsAccount.did, + }, + ) + expect(statusAfterInteractionTag.subjectStatuses[0].tags).toContain( + 'interaction-churn', + ) + + await emitModerationEvent({ + subject: bobsAccount, + event: { + $type: 'com.atproto.admin.defs#modEventTag', + remove: ['interaction-churn'], + add: ['follow-churn'], + }, + createdBy: sc.dids.alice, + }) + const { data: statusAfterFollowTag } = await queryModerationStatuses({ + subject: bobsAccount.did, + }) + + expect(statusAfterFollowTag.subjectStatuses[0].tags).not.toContain( + 'interaction-churn', + ) + expect(statusAfterFollowTag.subjectStatuses[0].tags).toContain( + 'follow-churn', + ) + }) + }) +}) diff --git a/packages/ozone/tests/moderation-statuses.test.ts b/packages/ozone/tests/moderation-statuses.test.ts index b4a2ed110a3..14184454e62 100644 --- a/packages/ozone/tests/moderation-statuses.test.ts +++ b/packages/ozone/tests/moderation-statuses.test.ts @@ -39,8 +39,8 @@ describe('moderation-statuses', () => { } const bobsPost = { $type: 'com.atproto.repo.strongRef', - uri: sc.posts[sc.dids.bob][1].ref.uriStr, - cid: sc.posts[sc.dids.bob][1].ref.cidStr, + uri: sc.posts[sc.dids.bob][0].ref.uriStr, + cid: sc.posts[sc.dids.bob][0].ref.cidStr, } const alicesPost = { $type: 'com.atproto.repo.strongRef', @@ -95,6 +95,23 @@ describe('moderation-statuses', () => { expect(forSnapshot(response.data.subjectStatuses)).toMatchSnapshot() }) + it('returns statuses filtered by subject language', async () => { + const klingonQueue = await queryModerationStatuses({ + tags: ['lang:i'], + }) + + expect(forSnapshot(klingonQueue.data.subjectStatuses)).toMatchSnapshot() + + const nonKlingonQueue = await queryModerationStatuses({ + excludeTags: ['lang:i'], + }) + + // Verify that the klingon tagged subject is not returned when excluding klingon + expect( + nonKlingonQueue.data.subjectStatuses.map((s) => s.id), + ).not.toContain(klingonQueue.data.subjectStatuses[0].id) + }) + it('returns paginated statuses', async () => { // We know there will be exactly 4 statuses in db const getPaginatedStatuses = async ( @@ -119,7 +136,7 @@ describe('moderation-statuses', () => { } const list = await getPaginatedStatuses({}) - expect(list[0].id).toEqual(4) + expect(list[0].id).toEqual(7) expect(list[list.length - 1].id).toEqual(1) await emitModerationEvent({ diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index a13f5fbf1f2..01a2f683d77 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -303,6 +303,12 @@ export const schemaDict = { type: 'string', format: 'datetime', }, + tags: { + type: 'array', + items: { + type: 'string', + }, + }, }, }, reportViewDetail: { @@ -897,6 +903,33 @@ export const schemaDict = { }, }, }, + modEventTag: { + type: 'object', + description: 'Add/Remove a tag on a subject', + required: ['add', 'remove'], + properties: { + add: { + type: 'array', + items: { + type: 'string', + }, + description: + "Tags to be added to the subject. If already exists, won't be duplicated.", + }, + remove: { + type: 'array', + items: { + type: 'string', + }, + description: + "Tags to be removed to the subject. Ignores a tag If it doesn't exist, won't be duplicated.", + }, + comment: { + type: 'string', + description: 'Additional comment about added/removed tags.', + }, + }, + }, communicationTemplateView: { type: 'object', required: [ @@ -1075,6 +1108,7 @@ export const schemaDict = { 'lex:com.atproto.admin.defs#modEventReverseTakedown', 'lex:com.atproto.admin.defs#modEventUnmute', 'lex:com.atproto.admin.defs#modEventEmail', + 'lex:com.atproto.admin.defs#modEventTag', ], }, subject: { @@ -1503,6 +1537,22 @@ export const schemaDict = { description: 'If specified, only events where all of these labels were removed are returned', }, + addedTags: { + type: 'array', + items: { + type: 'string', + }, + description: + 'If specified, only events where all of these tags were added are returned', + }, + removedTags: { + type: 'array', + items: { + type: 'string', + }, + description: + 'If specified, only events where all of these tags were removed are returned', + }, reportTypes: { type: 'array', items: { @@ -1620,6 +1670,18 @@ export const schemaDict = { maximum: 100, default: 50, }, + tags: { + type: 'array', + items: { + type: 'string', + }, + }, + excludeTags: { + type: 'array', + items: { + type: 'string', + }, + }, cursor: { type: 'string', }, diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/defs.ts b/packages/pds/src/lexicon/types/com/atproto/admin/defs.ts index 2fab0bc19a4..a713a635635 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/defs.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/defs.ts @@ -156,6 +156,7 @@ export interface SubjectStatusView { /** True indicates that the a previously taken moderator action was appealed against, by the author of the content. False indicates last appeal was resolved by moderators. */ appealed?: boolean suspendUntil?: string + tags?: string[] [k: string]: unknown } @@ -720,6 +721,29 @@ export function validateModEventEmail(v: unknown): ValidationResult { return lexicons.validate('com.atproto.admin.defs#modEventEmail', v) } +/** Add/Remove a tag on a subject */ +export interface ModEventTag { + /** Tags to be added to the subject. If already exists, won't be duplicated. */ + add: string[] + /** Tags to be removed to the subject. Ignores a tag If it doesn't exist, won't be duplicated. */ + remove: string[] + /** Additional comment about added/removed tags. */ + comment?: string + [k: string]: unknown +} + +export function isModEventTag(v: unknown): v is ModEventTag { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'com.atproto.admin.defs#modEventTag' + ) +} + +export function validateModEventTag(v: unknown): ValidationResult { + return lexicons.validate('com.atproto.admin.defs#modEventTag', v) +} + export interface CommunicationTemplateView { id: string /** Name of the template. */ diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts b/packages/pds/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts index 3c0acabd0eb..99d08c7f1b7 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts @@ -24,6 +24,7 @@ export interface InputSchema { | ComAtprotoAdminDefs.ModEventReverseTakedown | ComAtprotoAdminDefs.ModEventUnmute | ComAtprotoAdminDefs.ModEventEmail + | ComAtprotoAdminDefs.ModEventTag | { $type: string; [k: string]: unknown } subject: | ComAtprotoAdminDefs.RepoRef diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts b/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts index df07cfaf2da..9f4738578aa 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts @@ -31,6 +31,10 @@ export interface QueryParams { addedLabels?: string[] /** If specified, only events where all of these labels were removed are returned */ removedLabels?: string[] + /** If specified, only events where all of these tags were added are returned */ + addedTags?: string[] + /** If specified, only events where all of these tags were removed are returned */ + removedTags?: string[] reportTypes?: string[] cursor?: string } diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts b/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts index 0cc21c25352..f5031d25117 100644 --- a/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts +++ b/packages/pds/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts @@ -35,6 +35,8 @@ export interface QueryParams { /** Get subjects in unresolved appealed status */ appealed?: boolean limit: number + tags?: string[] + excludeTags?: string[] cursor?: string } diff --git a/packages/pds/tests/proxied/__snapshots__/admin.test.ts.snap b/packages/pds/tests/proxied/__snapshots__/admin.test.ts.snap index 5e9b4e0882a..285c24cf8f0 100644 --- a/packages/pds/tests/proxied/__snapshots__/admin.test.ts.snap +++ b/packages/pds/tests/proxied/__snapshots__/admin.test.ts.snap @@ -14,7 +14,7 @@ Array [ }, Object { "createdAt": "1970-01-01T00:00:00.000Z", - "id": 5, + "id": 6, "reason": "impersonation", "reasonType": "com.atproto.moderation.defs#reasonOther", "reportedBy": "user(2)", @@ -34,7 +34,7 @@ Array [ "event": Object { "$type": "com.atproto.admin.defs#modEventAcknowledge", }, - "id": 7, + "id": 9, "subject": Object { "$type": "com.atproto.admin.defs#repoRef", "did": "user(0)", @@ -51,7 +51,7 @@ Array [ "comment": "impersonation", "reportType": "com.atproto.moderation.defs#reasonOther", }, - "id": 5, + "id": 6, "subject": Object { "$type": "com.atproto.admin.defs#repoRef", "did": "user(0)", @@ -62,6 +62,25 @@ Array [ Object { "createdAt": "1970-01-01T00:00:00.000Z", "createdBy": "user(2)", + "event": Object { + "$type": "com.atproto.admin.defs#modEventTag", + "add": Array [ + "lang:en", + "lang:i", + ], + "remove": Array [], + }, + "id": 5, + "subject": Object { + "$type": "com.atproto.admin.defs#repoRef", + "did": "user(0)", + }, + "subjectBlobCids": Array [], + "subjectHandle": "bob.test", + }, + Object { + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "user(3)", "creatorHandle": "alice.test", "event": Object { "$type": "com.atproto.admin.defs#modEventReport", @@ -118,6 +137,10 @@ Object { }, "subjectBlobCids": Array [], "subjectRepoHandle": "bob.test", + "tags": Array [ + "lang:en", + "lang:i", + ], "takendown": false, "updatedAt": "1970-01-01T00:00:00.000Z", }, @@ -178,13 +201,32 @@ Object { exports[`proxies admin requests fetches moderation events. 1`] = ` Array [ + Object { + "createdAt": "1970-01-01T00:00:00.000Z", + "createdBy": "user(0)", + "event": Object { + "$type": "com.atproto.admin.defs#modEventTag", + "add": Array [ + "lang:und", + ], + "remove": Array [], + }, + "id": 8, + "subject": Object { + "$type": "com.atproto.repo.strongRef", + "cid": "cids(0)", + "uri": "record(0)", + }, + "subjectBlobCids": Array [], + "subjectHandle": "bob.test", + }, Object { "createdAt": "1970-01-01T00:00:00.000Z", "createdBy": "did:example:admin", "event": Object { "$type": "com.atproto.admin.defs#modEventAcknowledge", }, - "id": 6, + "id": 7, "subject": Object { "$type": "com.atproto.repo.strongRef", "cid": "cids(0)", @@ -206,7 +248,7 @@ Object { "moderation": Object { "subjectStatus": Object { "createdAt": "1970-01-01T00:00:00.000Z", - "id": 3, + "id": 4, "lastReviewedAt": "1970-01-01T00:00:00.000Z", "lastReviewedBy": "did:example:admin", "reviewState": "com.atproto.admin.defs#reviewClosed", @@ -217,6 +259,9 @@ Object { }, "subjectBlobCids": Array [], "subjectRepoHandle": "bob.test", + "tags": Array [ + "lang:und", + ], "takendown": false, "updatedAt": "1970-01-01T00:00:00.000Z", }, @@ -267,6 +312,10 @@ Object { }, "subjectBlobCids": Array [], "subjectRepoHandle": "bob.test", + "tags": Array [ + "lang:en", + "lang:i", + ], "takendown": false, "updatedAt": "1970-01-01T00:00:00.000Z", }, @@ -368,7 +417,7 @@ Object { "event": Object { "$type": "com.atproto.admin.defs#modEventAcknowledge", }, - "id": 6, + "id": 7, "subject": Object { "$type": "com.atproto.repo.strongRef", "cid": "cids(0)", @@ -385,7 +434,7 @@ Object { "event": Object { "$type": "com.atproto.admin.defs#modEventAcknowledge", }, - "id": 7, + "id": 9, "subject": Object { "$type": "com.atproto.admin.defs#repoRef", "did": "user(0)", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f13b09703e4..fba3901ac38 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,9 +1,5 @@ lockfileVersion: '6.0' -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - importers: .: @@ -11979,3 +11975,7 @@ packages: /zod@3.21.4: resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false From f79cc63390ae9dbd47a4ff5d694eec25b78b788e Mon Sep 17 00:00:00 2001 From: Foysal Ahamed Date: Mon, 19 Feb 2024 17:21:30 +0100 Subject: [PATCH 015/103] :memo: Add changesets for language filters (#2192) --- .changeset/soft-carrots-study.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/soft-carrots-study.md diff --git a/.changeset/soft-carrots-study.md b/.changeset/soft-carrots-study.md new file mode 100644 index 00000000000..9d353f2c4f1 --- /dev/null +++ b/.changeset/soft-carrots-study.md @@ -0,0 +1,6 @@ +--- +'@atproto/api': patch +'@atproto/ozone': patch +--- + +Tag event on moderation subjects and allow filtering events and subjects by tags From 1cce9ddb340a4ad82e017eeca5ad05a7653e0bf6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 11:27:59 -0500 Subject: [PATCH 016/103] Version packages (#2193) Co-authored-by: github-actions[bot] --- .changeset/soft-carrots-study.md | 6 ------ packages/api/CHANGELOG.md | 6 ++++++ packages/api/package.json | 2 +- packages/bsky/CHANGELOG.md | 7 +++++++ packages/bsky/package.json | 2 +- packages/dev-env/CHANGELOG.md | 10 ++++++++++ packages/dev-env/package.json | 2 +- packages/ozone/CHANGELOG.md | 9 +++++++++ packages/ozone/package.json | 2 +- packages/pds/CHANGELOG.md | 7 +++++++ packages/pds/package.json | 2 +- 11 files changed, 44 insertions(+), 11 deletions(-) delete mode 100644 .changeset/soft-carrots-study.md diff --git a/.changeset/soft-carrots-study.md b/.changeset/soft-carrots-study.md deleted file mode 100644 index 9d353f2c4f1..00000000000 --- a/.changeset/soft-carrots-study.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@atproto/api': patch -'@atproto/ozone': patch ---- - -Tag event on moderation subjects and allow filtering events and subjects by tags diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md index dfb163df8b6..f2117363038 100644 --- a/packages/api/CHANGELOG.md +++ b/packages/api/CHANGELOG.md @@ -1,5 +1,11 @@ # @atproto/api +## 0.9.8 + +### Patch Changes + +- [#2192](https://github.com/bluesky-social/atproto/pull/2192) [`f79cc6339`](https://github.com/bluesky-social/atproto/commit/f79cc63390ae9dbd47a4ff5d694eec25b78b788e) Thanks [@foysalit](https://github.com/foysalit)! - Tag event on moderation subjects and allow filtering events and subjects by tags + ## 0.9.7 ### Patch Changes diff --git a/packages/api/package.json b/packages/api/package.json index 71221da038a..4d803f3b83a 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/api", - "version": "0.9.7", + "version": "0.9.8", "license": "MIT", "description": "Client library for atproto and Bluesky", "keywords": [ diff --git a/packages/bsky/CHANGELOG.md b/packages/bsky/CHANGELOG.md index 070c81fef28..ea961d46b57 100644 --- a/packages/bsky/CHANGELOG.md +++ b/packages/bsky/CHANGELOG.md @@ -1,5 +1,12 @@ # @atproto/bsky +## 0.0.31 + +### Patch Changes + +- Updated dependencies [[`f79cc6339`](https://github.com/bluesky-social/atproto/commit/f79cc63390ae9dbd47a4ff5d694eec25b78b788e)]: + - @atproto/api@0.9.8 + ## 0.0.30 ### Patch Changes diff --git a/packages/bsky/package.json b/packages/bsky/package.json index f974353079e..2a7a78dc442 100644 --- a/packages/bsky/package.json +++ b/packages/bsky/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/bsky", - "version": "0.0.30", + "version": "0.0.31", "license": "MIT", "description": "Reference implementation of app.bsky App View (Bluesky API)", "keywords": [ diff --git a/packages/dev-env/CHANGELOG.md b/packages/dev-env/CHANGELOG.md index 004fa2029c1..3dadfec79b3 100644 --- a/packages/dev-env/CHANGELOG.md +++ b/packages/dev-env/CHANGELOG.md @@ -1,5 +1,15 @@ # @atproto/dev-env +## 0.2.31 + +### Patch Changes + +- Updated dependencies [[`f79cc6339`](https://github.com/bluesky-social/atproto/commit/f79cc63390ae9dbd47a4ff5d694eec25b78b788e)]: + - @atproto/api@0.9.8 + - @atproto/ozone@0.0.10 + - @atproto/bsky@0.0.31 + - @atproto/pds@0.3.19 + ## 0.2.30 ### Patch Changes diff --git a/packages/dev-env/package.json b/packages/dev-env/package.json index d2a7bbfda72..a0cf959d3ee 100644 --- a/packages/dev-env/package.json +++ b/packages/dev-env/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/dev-env", - "version": "0.2.30", + "version": "0.2.31", "license": "MIT", "description": "Local development environment helper for atproto development", "keywords": [ diff --git a/packages/ozone/CHANGELOG.md b/packages/ozone/CHANGELOG.md index 4af716d59a9..5d484430cd9 100644 --- a/packages/ozone/CHANGELOG.md +++ b/packages/ozone/CHANGELOG.md @@ -1,5 +1,14 @@ # @atproto/ozone +## 0.0.10 + +### Patch Changes + +- [#2192](https://github.com/bluesky-social/atproto/pull/2192) [`f79cc6339`](https://github.com/bluesky-social/atproto/commit/f79cc63390ae9dbd47a4ff5d694eec25b78b788e) Thanks [@foysalit](https://github.com/foysalit)! - Tag event on moderation subjects and allow filtering events and subjects by tags + +- Updated dependencies [[`f79cc6339`](https://github.com/bluesky-social/atproto/commit/f79cc63390ae9dbd47a4ff5d694eec25b78b788e)]: + - @atproto/api@0.9.8 + ## 0.0.9 ### Patch Changes diff --git a/packages/ozone/package.json b/packages/ozone/package.json index d0183c1061c..875118e2a56 100644 --- a/packages/ozone/package.json +++ b/packages/ozone/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/ozone", - "version": "0.0.9", + "version": "0.0.10", "license": "MIT", "description": "Backend service for moderating the Bluesky network.", "keywords": [ diff --git a/packages/pds/CHANGELOG.md b/packages/pds/CHANGELOG.md index a4e6c638010..82df25d1448 100644 --- a/packages/pds/CHANGELOG.md +++ b/packages/pds/CHANGELOG.md @@ -1,5 +1,12 @@ # @atproto/pds +## 0.3.19 + +### Patch Changes + +- Updated dependencies [[`f79cc6339`](https://github.com/bluesky-social/atproto/commit/f79cc63390ae9dbd47a4ff5d694eec25b78b788e)]: + - @atproto/api@0.9.8 + ## 0.3.18 ### Patch Changes diff --git a/packages/pds/package.json b/packages/pds/package.json index 48ba1356774..7476fc650ba 100644 --- a/packages/pds/package.json +++ b/packages/pds/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/pds", - "version": "0.3.18", + "version": "0.3.19", "license": "MIT", "description": "Reference implementation of atproto Personal Data Server (PDS)", "keywords": [ From 4c511b3d9de41ffeae3fc11db941e7df04f4468a Mon Sep 17 00:00:00 2001 From: Daniel Holmgren Date: Tue, 20 Feb 2024 19:23:56 -0600 Subject: [PATCH 017/103] Account migration schemas (#2170) * draft of account migration lexicons * format * clean up schemas * second pass on schemas * small fix * move around checkImportStatus * getServiceAuth * schema tweaks * format * update schemas * email flow for signed plc operation * refactor listMissingBlobs a bit * codegen * return did in describeServer * changeset --- .changeset/slimy-games-breathe.md | 5 + .../getRecommendedDidCredentials.json | 29 ++ .../requestPlcOperationSignature.json | 10 + .../atproto/identity/signPlcOperation.json | 45 +++ .../atproto/identity/submitPlcOperation.json | 20 + lexicons/com/atproto/repo/importRepo.json | 13 + .../com/atproto/repo/listMissingBlobs.json | 44 +++ .../com/atproto/server/activateAccount.json | 10 + .../atproto/server/checkAccountStatus.json | 38 ++ .../com/atproto/server/deactivateAccount.json | 23 ++ .../com/atproto/server/describeServer.json | 6 +- .../com/atproto/server/getServiceAuth.json | 33 ++ packages/api/src/client/index.ts | 140 +++++++ packages/api/src/client/lexicons.ts | 348 +++++++++++++++++- .../identity/getRecommendedDidCredentials.ts | 37 ++ .../identity/requestPlcOperationSignature.ts | 28 ++ .../com/atproto/identity/signPlcOperation.ts | 44 +++ .../atproto/identity/submitPlcOperation.ts | 32 ++ .../types/com/atproto/repo/importRepo.ts | 29 ++ .../com/atproto/repo/listMissingBlobs.ts | 55 +++ .../com/atproto/server/activateAccount.ts | 28 ++ .../com/atproto/server/checkAccountStatus.ts | 41 +++ .../com/atproto/server/deactivateAccount.ts | 33 ++ .../com/atproto/server/describeServer.ts | 1 + .../com/atproto/server/getServiceAuth.ts | 36 ++ packages/bsky/src/lexicon/index.ts | 124 +++++++ packages/bsky/src/lexicon/lexicons.ts | 348 +++++++++++++++++- .../identity/getRecommendedDidCredentials.ts | 47 +++ .../identity/requestPlcOperationSignature.ts | 31 ++ .../com/atproto/identity/signPlcOperation.ts | 55 +++ .../atproto/identity/submitPlcOperation.ts | 38 ++ .../types/com/atproto/repo/importRepo.ts | 36 ++ .../com/atproto/repo/listMissingBlobs.ts | 65 ++++ .../com/atproto/server/activateAccount.ts | 31 ++ .../com/atproto/server/checkAccountStatus.ts | 51 +++ .../com/atproto/server/deactivateAccount.ts | 39 ++ .../com/atproto/server/describeServer.ts | 1 + .../com/atproto/server/getServiceAuth.ts | 46 +++ packages/ozone/src/lexicon/index.ts | 124 +++++++ packages/ozone/src/lexicon/lexicons.ts | 348 +++++++++++++++++- .../identity/getRecommendedDidCredentials.ts | 47 +++ .../identity/requestPlcOperationSignature.ts | 31 ++ .../com/atproto/identity/signPlcOperation.ts | 55 +++ .../atproto/identity/submitPlcOperation.ts | 38 ++ .../types/com/atproto/repo/importRepo.ts | 36 ++ .../com/atproto/repo/listMissingBlobs.ts | 65 ++++ .../com/atproto/server/activateAccount.ts | 31 ++ .../com/atproto/server/checkAccountStatus.ts | 51 +++ .../com/atproto/server/deactivateAccount.ts | 39 ++ .../com/atproto/server/describeServer.ts | 1 + .../com/atproto/server/getServiceAuth.ts | 46 +++ .../api/com/atproto/server/describeServer.ts | 1 + packages/pds/src/lexicon/index.ts | 124 +++++++ packages/pds/src/lexicon/lexicons.ts | 348 +++++++++++++++++- .../identity/getRecommendedDidCredentials.ts | 47 +++ .../identity/requestPlcOperationSignature.ts | 31 ++ .../com/atproto/identity/signPlcOperation.ts | 55 +++ .../atproto/identity/submitPlcOperation.ts | 38 ++ .../types/com/atproto/repo/importRepo.ts | 36 ++ .../com/atproto/repo/listMissingBlobs.ts | 65 ++++ .../com/atproto/server/activateAccount.ts | 31 ++ .../com/atproto/server/checkAccountStatus.ts | 51 +++ .../com/atproto/server/deactivateAccount.ts | 39 ++ .../com/atproto/server/describeServer.ts | 1 + .../com/atproto/server/getServiceAuth.ts | 46 +++ 65 files changed, 3860 insertions(+), 5 deletions(-) create mode 100644 .changeset/slimy-games-breathe.md create mode 100644 lexicons/com/atproto/identity/getRecommendedDidCredentials.json create mode 100644 lexicons/com/atproto/identity/requestPlcOperationSignature.json create mode 100644 lexicons/com/atproto/identity/signPlcOperation.json create mode 100644 lexicons/com/atproto/identity/submitPlcOperation.json create mode 100644 lexicons/com/atproto/repo/importRepo.json create mode 100644 lexicons/com/atproto/repo/listMissingBlobs.json create mode 100644 lexicons/com/atproto/server/activateAccount.json create mode 100644 lexicons/com/atproto/server/checkAccountStatus.json create mode 100644 lexicons/com/atproto/server/deactivateAccount.json create mode 100644 lexicons/com/atproto/server/getServiceAuth.json create mode 100644 packages/api/src/client/types/com/atproto/identity/getRecommendedDidCredentials.ts create mode 100644 packages/api/src/client/types/com/atproto/identity/requestPlcOperationSignature.ts create mode 100644 packages/api/src/client/types/com/atproto/identity/signPlcOperation.ts create mode 100644 packages/api/src/client/types/com/atproto/identity/submitPlcOperation.ts create mode 100644 packages/api/src/client/types/com/atproto/repo/importRepo.ts create mode 100644 packages/api/src/client/types/com/atproto/repo/listMissingBlobs.ts create mode 100644 packages/api/src/client/types/com/atproto/server/activateAccount.ts create mode 100644 packages/api/src/client/types/com/atproto/server/checkAccountStatus.ts create mode 100644 packages/api/src/client/types/com/atproto/server/deactivateAccount.ts create mode 100644 packages/api/src/client/types/com/atproto/server/getServiceAuth.ts create mode 100644 packages/bsky/src/lexicon/types/com/atproto/identity/getRecommendedDidCredentials.ts create mode 100644 packages/bsky/src/lexicon/types/com/atproto/identity/requestPlcOperationSignature.ts create mode 100644 packages/bsky/src/lexicon/types/com/atproto/identity/signPlcOperation.ts create mode 100644 packages/bsky/src/lexicon/types/com/atproto/identity/submitPlcOperation.ts create mode 100644 packages/bsky/src/lexicon/types/com/atproto/repo/importRepo.ts create mode 100644 packages/bsky/src/lexicon/types/com/atproto/repo/listMissingBlobs.ts create mode 100644 packages/bsky/src/lexicon/types/com/atproto/server/activateAccount.ts create mode 100644 packages/bsky/src/lexicon/types/com/atproto/server/checkAccountStatus.ts create mode 100644 packages/bsky/src/lexicon/types/com/atproto/server/deactivateAccount.ts create mode 100644 packages/bsky/src/lexicon/types/com/atproto/server/getServiceAuth.ts create mode 100644 packages/ozone/src/lexicon/types/com/atproto/identity/getRecommendedDidCredentials.ts create mode 100644 packages/ozone/src/lexicon/types/com/atproto/identity/requestPlcOperationSignature.ts create mode 100644 packages/ozone/src/lexicon/types/com/atproto/identity/signPlcOperation.ts create mode 100644 packages/ozone/src/lexicon/types/com/atproto/identity/submitPlcOperation.ts create mode 100644 packages/ozone/src/lexicon/types/com/atproto/repo/importRepo.ts create mode 100644 packages/ozone/src/lexicon/types/com/atproto/repo/listMissingBlobs.ts create mode 100644 packages/ozone/src/lexicon/types/com/atproto/server/activateAccount.ts create mode 100644 packages/ozone/src/lexicon/types/com/atproto/server/checkAccountStatus.ts create mode 100644 packages/ozone/src/lexicon/types/com/atproto/server/deactivateAccount.ts create mode 100644 packages/ozone/src/lexicon/types/com/atproto/server/getServiceAuth.ts create mode 100644 packages/pds/src/lexicon/types/com/atproto/identity/getRecommendedDidCredentials.ts create mode 100644 packages/pds/src/lexicon/types/com/atproto/identity/requestPlcOperationSignature.ts create mode 100644 packages/pds/src/lexicon/types/com/atproto/identity/signPlcOperation.ts create mode 100644 packages/pds/src/lexicon/types/com/atproto/identity/submitPlcOperation.ts create mode 100644 packages/pds/src/lexicon/types/com/atproto/repo/importRepo.ts create mode 100644 packages/pds/src/lexicon/types/com/atproto/repo/listMissingBlobs.ts create mode 100644 packages/pds/src/lexicon/types/com/atproto/server/activateAccount.ts create mode 100644 packages/pds/src/lexicon/types/com/atproto/server/checkAccountStatus.ts create mode 100644 packages/pds/src/lexicon/types/com/atproto/server/deactivateAccount.ts create mode 100644 packages/pds/src/lexicon/types/com/atproto/server/getServiceAuth.ts diff --git a/.changeset/slimy-games-breathe.md b/.changeset/slimy-games-breathe.md new file mode 100644 index 00000000000..7551f0cb235 --- /dev/null +++ b/.changeset/slimy-games-breathe.md @@ -0,0 +1,5 @@ +--- +'@atproto/api': minor +--- + +Add lexicons and methods for account migration diff --git a/lexicons/com/atproto/identity/getRecommendedDidCredentials.json b/lexicons/com/atproto/identity/getRecommendedDidCredentials.json new file mode 100644 index 00000000000..3506dbec351 --- /dev/null +++ b/lexicons/com/atproto/identity/getRecommendedDidCredentials.json @@ -0,0 +1,29 @@ +{ + "lexicon": 1, + "id": "com.atproto.identity.getRecommendedDidCredentials", + "defs": { + "main": { + "type": "query", + "description": "Describe the credentials that should be included in the DID doc of an account that is migrating to this service.", + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": { + "rotationKeys": { + "description": "Recommended rotation keys for PLC dids. Should be undefined (or ignored) for did:webs.", + "type": "array", + "items": { "type": "string" } + }, + "alsoKnownAs": { + "type": "array", + "items": { "type": "string" } + }, + "verificationMethods": { "type": "unknown" }, + "services": { "type": "unknown" } + } + } + } + } + } +} diff --git a/lexicons/com/atproto/identity/requestPlcOperationSignature.json b/lexicons/com/atproto/identity/requestPlcOperationSignature.json new file mode 100644 index 00000000000..4aa8b18fee9 --- /dev/null +++ b/lexicons/com/atproto/identity/requestPlcOperationSignature.json @@ -0,0 +1,10 @@ +{ + "lexicon": 1, + "id": "com.atproto.identity.requestPlcOperationSignature", + "defs": { + "main": { + "type": "procedure", + "description": "Request an email with a code to in order to request a signed PLC operation. Requires Auth." + } + } +} diff --git a/lexicons/com/atproto/identity/signPlcOperation.json b/lexicons/com/atproto/identity/signPlcOperation.json new file mode 100644 index 00000000000..05a952cab6c --- /dev/null +++ b/lexicons/com/atproto/identity/signPlcOperation.json @@ -0,0 +1,45 @@ +{ + "lexicon": 1, + "id": "com.atproto.identity.signPlcOperation", + "defs": { + "main": { + "type": "procedure", + "description": "Signs a PLC operation to update some value(s) in the requesting DID's document.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": { + "token": { + "description": "A token received through com.atproto.identity.requestPlcOperationSignature", + "type": "string" + }, + "rotationKeys": { + "type": "array", + "items": { "type": "string" } + }, + "alsoKnownAs": { + "type": "array", + "items": { "type": "string" } + }, + "verificationMethods": { "type": "unknown" }, + "services": { "type": "unknown" } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["operation"], + "properties": { + "operation": { + "type": "unknown", + "description": "A signed DID PLC operation." + } + } + } + } + } + } +} diff --git a/lexicons/com/atproto/identity/submitPlcOperation.json b/lexicons/com/atproto/identity/submitPlcOperation.json new file mode 100644 index 00000000000..280f5003b0d --- /dev/null +++ b/lexicons/com/atproto/identity/submitPlcOperation.json @@ -0,0 +1,20 @@ +{ + "lexicon": 1, + "id": "com.atproto.identity.submitPlcOperation", + "defs": { + "main": { + "type": "procedure", + "description": "Validates a PLC operation to ensure that it doesn't violate a service's constraints or get the identity into a bad state, then submits it to the PLC registry", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["operation"], + "properties": { + "operation": { "type": "unknown" } + } + } + } + } + } +} diff --git a/lexicons/com/atproto/repo/importRepo.json b/lexicons/com/atproto/repo/importRepo.json new file mode 100644 index 00000000000..fc850b1a2b6 --- /dev/null +++ b/lexicons/com/atproto/repo/importRepo.json @@ -0,0 +1,13 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.importRepo", + "defs": { + "main": { + "type": "procedure", + "description": "Import a repo in the form of a CAR file. Requires Content-Length HTTP header to be set.", + "input": { + "encoding": "application/vnd.ipld.car" + } + } + } +} diff --git a/lexicons/com/atproto/repo/listMissingBlobs.json b/lexicons/com/atproto/repo/listMissingBlobs.json new file mode 100644 index 00000000000..c39913d566c --- /dev/null +++ b/lexicons/com/atproto/repo/listMissingBlobs.json @@ -0,0 +1,44 @@ +{ + "lexicon": 1, + "id": "com.atproto.repo.listMissingBlobs", + "defs": { + "main": { + "type": "query", + "description": "Returns a list of missing blobs for the requesting account. Intended to be used in the account migration flow.", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 1000, + "default": 500 + }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["blobs"], + "properties": { + "cursor": { "type": "string" }, + "blobs": { + "type": "array", + "items": { "type": "ref", "ref": "#recordBlob" } + } + } + } + } + }, + "recordBlob": { + "type": "object", + "required": ["cid", "recordUri"], + "properties": { + "cid": { "type": "string", "format": "cid" }, + "recordUri": { "type": "string", "format": "at-uri" } + } + } + } +} diff --git a/lexicons/com/atproto/server/activateAccount.json b/lexicons/com/atproto/server/activateAccount.json new file mode 100644 index 00000000000..e06935fcd07 --- /dev/null +++ b/lexicons/com/atproto/server/activateAccount.json @@ -0,0 +1,10 @@ +{ + "lexicon": 1, + "id": "com.atproto.server.activateAccount", + "defs": { + "main": { + "type": "procedure", + "description": "Activates a currently deactivated account. Used to finalize account migration after the account's repo is imported and identity is setup." + } + } +} diff --git a/lexicons/com/atproto/server/checkAccountStatus.json b/lexicons/com/atproto/server/checkAccountStatus.json new file mode 100644 index 00000000000..d34596e60e1 --- /dev/null +++ b/lexicons/com/atproto/server/checkAccountStatus.json @@ -0,0 +1,38 @@ +{ + "lexicon": 1, + "id": "com.atproto.server.checkAccountStatus", + "defs": { + "main": { + "type": "query", + "description": "Returns the status of an account, especially as pertaining to import or recovery. Can be called many times over the course of an account migration. Requires auth and can only be called pertaining to oneself.", + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "activated", + "validDid", + "repoCommit", + "repoRev", + "repoBlocks", + "indexedRecords", + "privateStateValues", + "expectedBlobs", + "importedBlobs" + ], + "properties": { + "activated": { "type": "boolean" }, + "validDid": { "type": "boolean" }, + "repoCommit": { "type": "string", "format": "cid" }, + "repoRev": { "type": "string" }, + "repoBlocks": { "type": "integer" }, + "indexedRecords": { "type": "integer" }, + "privateStateValues": { "type": "integer" }, + "expectedBlobs": { "type": "integer" }, + "importedBlobs": { "type": "integer" } + } + } + } + } + } +} diff --git a/lexicons/com/atproto/server/deactivateAccount.json b/lexicons/com/atproto/server/deactivateAccount.json new file mode 100644 index 00000000000..698fccf202e --- /dev/null +++ b/lexicons/com/atproto/server/deactivateAccount.json @@ -0,0 +1,23 @@ +{ + "lexicon": 1, + "id": "com.atproto.server.deactivateAccount", + "defs": { + "main": { + "type": "procedure", + "description": "Deactivates a currently active account. Stops serving of repo, and future writes to repo until reactivated. Used to finalize account migration with the old host after the account has been activated on the new host.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": { + "deleteAfter": { + "type": "string", + "format": "datetime", + "description": "A recommendation to server as to how long they should hold onto the deactivated account before deleting." + } + } + } + } + } + } +} diff --git a/lexicons/com/atproto/server/describeServer.json b/lexicons/com/atproto/server/describeServer.json index 908cb2127f9..d7bd4ab76f2 100644 --- a/lexicons/com/atproto/server/describeServer.json +++ b/lexicons/com/atproto/server/describeServer.json @@ -9,7 +9,7 @@ "encoding": "application/json", "schema": { "type": "object", - "required": ["availableUserDomains"], + "required": ["did", "availableUserDomains"], "properties": { "inviteCodeRequired": { "type": "boolean", @@ -28,6 +28,10 @@ "type": "ref", "description": "URLs of service policy documents.", "ref": "#links" + }, + "did": { + "type": "string", + "format": "did" } } } diff --git a/lexicons/com/atproto/server/getServiceAuth.json b/lexicons/com/atproto/server/getServiceAuth.json new file mode 100644 index 00000000000..95984c186ad --- /dev/null +++ b/lexicons/com/atproto/server/getServiceAuth.json @@ -0,0 +1,33 @@ +{ + "lexicon": 1, + "id": "com.atproto.server.getServiceAuth", + "defs": { + "main": { + "type": "query", + "description": "Get a signed token on behalf of the requesting DID for the requested service.", + "parameters": { + "type": "params", + "required": ["aud"], + "properties": { + "aud": { + "type": "string", + "format": "did", + "description": "The DID of the service that the token will be used to authenticate with" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["token"], + "properties": { + "token": { + "type": "string" + } + } + } + } + } + } +} diff --git a/packages/api/src/client/index.ts b/packages/api/src/client/index.ts index 35c784cbea3..42f31866dae 100644 --- a/packages/api/src/client/index.ts +++ b/packages/api/src/client/index.ts @@ -31,7 +31,11 @@ import * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/up import * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle' import * as ComAtprotoAdminUpdateCommunicationTemplate from './types/com/atproto/admin/updateCommunicationTemplate' import * as ComAtprotoAdminUpdateSubjectStatus from './types/com/atproto/admin/updateSubjectStatus' +import * as ComAtprotoIdentityGetRecommendedDidCredentials from './types/com/atproto/identity/getRecommendedDidCredentials' +import * as ComAtprotoIdentityRequestPlcOperationSignature from './types/com/atproto/identity/requestPlcOperationSignature' import * as ComAtprotoIdentityResolveHandle from './types/com/atproto/identity/resolveHandle' +import * as ComAtprotoIdentitySignPlcOperation from './types/com/atproto/identity/signPlcOperation' +import * as ComAtprotoIdentitySubmitPlcOperation from './types/com/atproto/identity/submitPlcOperation' import * as ComAtprotoIdentityUpdateHandle from './types/com/atproto/identity/updateHandle' import * as ComAtprotoLabelDefs from './types/com/atproto/label/defs' import * as ComAtprotoLabelQueryLabels from './types/com/atproto/label/queryLabels' @@ -43,21 +47,27 @@ import * as ComAtprotoRepoCreateRecord from './types/com/atproto/repo/createReco import * as ComAtprotoRepoDeleteRecord from './types/com/atproto/repo/deleteRecord' import * as ComAtprotoRepoDescribeRepo from './types/com/atproto/repo/describeRepo' import * as ComAtprotoRepoGetRecord from './types/com/atproto/repo/getRecord' +import * as ComAtprotoRepoImportRepo from './types/com/atproto/repo/importRepo' +import * as ComAtprotoRepoListMissingBlobs from './types/com/atproto/repo/listMissingBlobs' import * as ComAtprotoRepoListRecords from './types/com/atproto/repo/listRecords' import * as ComAtprotoRepoPutRecord from './types/com/atproto/repo/putRecord' import * as ComAtprotoRepoStrongRef from './types/com/atproto/repo/strongRef' import * as ComAtprotoRepoUploadBlob from './types/com/atproto/repo/uploadBlob' +import * as ComAtprotoServerActivateAccount from './types/com/atproto/server/activateAccount' +import * as ComAtprotoServerCheckAccountStatus from './types/com/atproto/server/checkAccountStatus' import * as ComAtprotoServerConfirmEmail from './types/com/atproto/server/confirmEmail' import * as ComAtprotoServerCreateAccount from './types/com/atproto/server/createAccount' import * as ComAtprotoServerCreateAppPassword from './types/com/atproto/server/createAppPassword' import * as ComAtprotoServerCreateInviteCode from './types/com/atproto/server/createInviteCode' import * as ComAtprotoServerCreateInviteCodes from './types/com/atproto/server/createInviteCodes' import * as ComAtprotoServerCreateSession from './types/com/atproto/server/createSession' +import * as ComAtprotoServerDeactivateAccount from './types/com/atproto/server/deactivateAccount' import * as ComAtprotoServerDefs from './types/com/atproto/server/defs' import * as ComAtprotoServerDeleteAccount from './types/com/atproto/server/deleteAccount' import * as ComAtprotoServerDeleteSession from './types/com/atproto/server/deleteSession' import * as ComAtprotoServerDescribeServer from './types/com/atproto/server/describeServer' import * as ComAtprotoServerGetAccountInviteCodes from './types/com/atproto/server/getAccountInviteCodes' +import * as ComAtprotoServerGetServiceAuth from './types/com/atproto/server/getServiceAuth' import * as ComAtprotoServerGetSession from './types/com/atproto/server/getSession' import * as ComAtprotoServerListAppPasswords from './types/com/atproto/server/listAppPasswords' import * as ComAtprotoServerRefreshSession from './types/com/atproto/server/refreshSession' @@ -177,7 +187,11 @@ export * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/up export * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle' export * as ComAtprotoAdminUpdateCommunicationTemplate from './types/com/atproto/admin/updateCommunicationTemplate' export * as ComAtprotoAdminUpdateSubjectStatus from './types/com/atproto/admin/updateSubjectStatus' +export * as ComAtprotoIdentityGetRecommendedDidCredentials from './types/com/atproto/identity/getRecommendedDidCredentials' +export * as ComAtprotoIdentityRequestPlcOperationSignature from './types/com/atproto/identity/requestPlcOperationSignature' export * as ComAtprotoIdentityResolveHandle from './types/com/atproto/identity/resolveHandle' +export * as ComAtprotoIdentitySignPlcOperation from './types/com/atproto/identity/signPlcOperation' +export * as ComAtprotoIdentitySubmitPlcOperation from './types/com/atproto/identity/submitPlcOperation' export * as ComAtprotoIdentityUpdateHandle from './types/com/atproto/identity/updateHandle' export * as ComAtprotoLabelDefs from './types/com/atproto/label/defs' export * as ComAtprotoLabelQueryLabels from './types/com/atproto/label/queryLabels' @@ -189,21 +203,27 @@ export * as ComAtprotoRepoCreateRecord from './types/com/atproto/repo/createReco export * as ComAtprotoRepoDeleteRecord from './types/com/atproto/repo/deleteRecord' export * as ComAtprotoRepoDescribeRepo from './types/com/atproto/repo/describeRepo' export * as ComAtprotoRepoGetRecord from './types/com/atproto/repo/getRecord' +export * as ComAtprotoRepoImportRepo from './types/com/atproto/repo/importRepo' +export * as ComAtprotoRepoListMissingBlobs from './types/com/atproto/repo/listMissingBlobs' export * as ComAtprotoRepoListRecords from './types/com/atproto/repo/listRecords' export * as ComAtprotoRepoPutRecord from './types/com/atproto/repo/putRecord' export * as ComAtprotoRepoStrongRef from './types/com/atproto/repo/strongRef' export * as ComAtprotoRepoUploadBlob from './types/com/atproto/repo/uploadBlob' +export * as ComAtprotoServerActivateAccount from './types/com/atproto/server/activateAccount' +export * as ComAtprotoServerCheckAccountStatus from './types/com/atproto/server/checkAccountStatus' export * as ComAtprotoServerConfirmEmail from './types/com/atproto/server/confirmEmail' export * as ComAtprotoServerCreateAccount from './types/com/atproto/server/createAccount' export * as ComAtprotoServerCreateAppPassword from './types/com/atproto/server/createAppPassword' export * as ComAtprotoServerCreateInviteCode from './types/com/atproto/server/createInviteCode' export * as ComAtprotoServerCreateInviteCodes from './types/com/atproto/server/createInviteCodes' export * as ComAtprotoServerCreateSession from './types/com/atproto/server/createSession' +export * as ComAtprotoServerDeactivateAccount from './types/com/atproto/server/deactivateAccount' export * as ComAtprotoServerDefs from './types/com/atproto/server/defs' export * as ComAtprotoServerDeleteAccount from './types/com/atproto/server/deleteAccount' export * as ComAtprotoServerDeleteSession from './types/com/atproto/server/deleteSession' export * as ComAtprotoServerDescribeServer from './types/com/atproto/server/describeServer' export * as ComAtprotoServerGetAccountInviteCodes from './types/com/atproto/server/getAccountInviteCodes' +export * as ComAtprotoServerGetServiceAuth from './types/com/atproto/server/getServiceAuth' export * as ComAtprotoServerGetSession from './types/com/atproto/server/getSession' export * as ComAtprotoServerListAppPasswords from './types/com/atproto/server/listAppPasswords' export * as ComAtprotoServerRefreshSession from './types/com/atproto/server/refreshSession' @@ -675,6 +695,38 @@ export class ComAtprotoIdentityNS { this._service = service } + getRecommendedDidCredentials( + params?: ComAtprotoIdentityGetRecommendedDidCredentials.QueryParams, + opts?: ComAtprotoIdentityGetRecommendedDidCredentials.CallOptions, + ): Promise { + return this._service.xrpc + .call( + 'com.atproto.identity.getRecommendedDidCredentials', + params, + undefined, + opts, + ) + .catch((e) => { + throw ComAtprotoIdentityGetRecommendedDidCredentials.toKnownErr(e) + }) + } + + requestPlcOperationSignature( + data?: ComAtprotoIdentityRequestPlcOperationSignature.InputSchema, + opts?: ComAtprotoIdentityRequestPlcOperationSignature.CallOptions, + ): Promise { + return this._service.xrpc + .call( + 'com.atproto.identity.requestPlcOperationSignature', + opts?.qp, + data, + opts, + ) + .catch((e) => { + throw ComAtprotoIdentityRequestPlcOperationSignature.toKnownErr(e) + }) + } + resolveHandle( params?: ComAtprotoIdentityResolveHandle.QueryParams, opts?: ComAtprotoIdentityResolveHandle.CallOptions, @@ -686,6 +738,28 @@ export class ComAtprotoIdentityNS { }) } + signPlcOperation( + data?: ComAtprotoIdentitySignPlcOperation.InputSchema, + opts?: ComAtprotoIdentitySignPlcOperation.CallOptions, + ): Promise { + return this._service.xrpc + .call('com.atproto.identity.signPlcOperation', opts?.qp, data, opts) + .catch((e) => { + throw ComAtprotoIdentitySignPlcOperation.toKnownErr(e) + }) + } + + submitPlcOperation( + data?: ComAtprotoIdentitySubmitPlcOperation.InputSchema, + opts?: ComAtprotoIdentitySubmitPlcOperation.CallOptions, + ): Promise { + return this._service.xrpc + .call('com.atproto.identity.submitPlcOperation', opts?.qp, data, opts) + .catch((e) => { + throw ComAtprotoIdentitySubmitPlcOperation.toKnownErr(e) + }) + } + updateHandle( data?: ComAtprotoIdentityUpdateHandle.InputSchema, opts?: ComAtprotoIdentityUpdateHandle.CallOptions, @@ -798,6 +872,28 @@ export class ComAtprotoRepoNS { }) } + importRepo( + data?: ComAtprotoRepoImportRepo.InputSchema, + opts?: ComAtprotoRepoImportRepo.CallOptions, + ): Promise { + return this._service.xrpc + .call('com.atproto.repo.importRepo', opts?.qp, data, opts) + .catch((e) => { + throw ComAtprotoRepoImportRepo.toKnownErr(e) + }) + } + + listMissingBlobs( + params?: ComAtprotoRepoListMissingBlobs.QueryParams, + opts?: ComAtprotoRepoListMissingBlobs.CallOptions, + ): Promise { + return this._service.xrpc + .call('com.atproto.repo.listMissingBlobs', params, undefined, opts) + .catch((e) => { + throw ComAtprotoRepoListMissingBlobs.toKnownErr(e) + }) + } + listRecords( params?: ComAtprotoRepoListRecords.QueryParams, opts?: ComAtprotoRepoListRecords.CallOptions, @@ -839,6 +935,28 @@ export class ComAtprotoServerNS { this._service = service } + activateAccount( + data?: ComAtprotoServerActivateAccount.InputSchema, + opts?: ComAtprotoServerActivateAccount.CallOptions, + ): Promise { + return this._service.xrpc + .call('com.atproto.server.activateAccount', opts?.qp, data, opts) + .catch((e) => { + throw ComAtprotoServerActivateAccount.toKnownErr(e) + }) + } + + checkAccountStatus( + params?: ComAtprotoServerCheckAccountStatus.QueryParams, + opts?: ComAtprotoServerCheckAccountStatus.CallOptions, + ): Promise { + return this._service.xrpc + .call('com.atproto.server.checkAccountStatus', params, undefined, opts) + .catch((e) => { + throw ComAtprotoServerCheckAccountStatus.toKnownErr(e) + }) + } + confirmEmail( data?: ComAtprotoServerConfirmEmail.InputSchema, opts?: ComAtprotoServerConfirmEmail.CallOptions, @@ -905,6 +1023,17 @@ export class ComAtprotoServerNS { }) } + deactivateAccount( + data?: ComAtprotoServerDeactivateAccount.InputSchema, + opts?: ComAtprotoServerDeactivateAccount.CallOptions, + ): Promise { + return this._service.xrpc + .call('com.atproto.server.deactivateAccount', opts?.qp, data, opts) + .catch((e) => { + throw ComAtprotoServerDeactivateAccount.toKnownErr(e) + }) + } + deleteAccount( data?: ComAtprotoServerDeleteAccount.InputSchema, opts?: ComAtprotoServerDeleteAccount.CallOptions, @@ -949,6 +1078,17 @@ export class ComAtprotoServerNS { }) } + getServiceAuth( + params?: ComAtprotoServerGetServiceAuth.QueryParams, + opts?: ComAtprotoServerGetServiceAuth.CallOptions, + ): Promise { + return this._service.xrpc + .call('com.atproto.server.getServiceAuth', params, undefined, opts) + .catch((e) => { + throw ComAtprotoServerGetServiceAuth.toKnownErr(e) + }) + } + getSession( params?: ComAtprotoServerGetSession.QueryParams, opts?: ComAtprotoServerGetSession.CallOptions, diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index 01a2f683d77..a3491462d50 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -1968,6 +1968,56 @@ export const schemaDict = { }, }, }, + ComAtprotoIdentityGetRecommendedDidCredentials: { + lexicon: 1, + id: 'com.atproto.identity.getRecommendedDidCredentials', + defs: { + main: { + type: 'query', + description: + 'Describe the credentials that should be included in the DID doc of an account that is migrating to this service.', + output: { + encoding: 'application/json', + schema: { + type: 'object', + properties: { + rotationKeys: { + description: + 'Recommended rotation keys for PLC dids. Should be undefined (or ignored) for did:webs.', + type: 'array', + items: { + type: 'string', + }, + }, + alsoKnownAs: { + type: 'array', + items: { + type: 'string', + }, + }, + verificationMethods: { + type: 'unknown', + }, + services: { + type: 'unknown', + }, + }, + }, + }, + }, + }, + }, + ComAtprotoIdentityRequestPlcOperationSignature: { + lexicon: 1, + id: 'com.atproto.identity.requestPlcOperationSignature', + defs: { + main: { + type: 'procedure', + description: + 'Request an email with a code to in order to request a signed PLC operation. Requires Auth.', + }, + }, + }, ComAtprotoIdentityResolveHandle: { lexicon: 1, id: 'com.atproto.identity.resolveHandle', @@ -2002,6 +2052,84 @@ export const schemaDict = { }, }, }, + ComAtprotoIdentitySignPlcOperation: { + lexicon: 1, + id: 'com.atproto.identity.signPlcOperation', + defs: { + main: { + type: 'procedure', + description: + "Signs a PLC operation to update some value(s) in the requesting DID's document.", + input: { + encoding: 'application/json', + schema: { + type: 'object', + properties: { + token: { + description: + 'A token received through com.atproto.identity.requestPlcOperationSignature', + type: 'string', + }, + rotationKeys: { + type: 'array', + items: { + type: 'string', + }, + }, + alsoKnownAs: { + type: 'array', + items: { + type: 'string', + }, + }, + verificationMethods: { + type: 'unknown', + }, + services: { + type: 'unknown', + }, + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['operation'], + properties: { + operation: { + type: 'unknown', + description: 'A signed DID PLC operation.', + }, + }, + }, + }, + }, + }, + }, + ComAtprotoIdentitySubmitPlcOperation: { + lexicon: 1, + id: 'com.atproto.identity.submitPlcOperation', + defs: { + main: { + type: 'procedure', + description: + "Validates a PLC operation to ensure that it doesn't violate a service's constraints or get the identity into a bad state, then submits it to the PLC registry", + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['operation'], + properties: { + operation: { + type: 'unknown', + }, + }, + }, + }, + }, + }, + }, ComAtprotoIdentityUpdateHandle: { lexicon: 1, id: 'com.atproto.identity.updateHandle', @@ -2710,6 +2838,78 @@ export const schemaDict = { }, }, }, + ComAtprotoRepoImportRepo: { + lexicon: 1, + id: 'com.atproto.repo.importRepo', + defs: { + main: { + type: 'procedure', + description: + 'Import a repo in the form of a CAR file. Requires Content-Length HTTP header to be set.', + input: { + encoding: 'application/vnd.ipld.car', + }, + }, + }, + }, + ComAtprotoRepoListMissingBlobs: { + lexicon: 1, + id: 'com.atproto.repo.listMissingBlobs', + defs: { + main: { + type: 'query', + description: + 'Returns a list of missing blobs for the requesting account. Intended to be used in the account migration flow.', + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 1000, + default: 500, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['blobs'], + properties: { + cursor: { + type: 'string', + }, + blobs: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:com.atproto.repo.listMissingBlobs#recordBlob', + }, + }, + }, + }, + }, + }, + recordBlob: { + type: 'object', + required: ['cid', 'recordUri'], + properties: { + cid: { + type: 'string', + format: 'cid', + }, + recordUri: { + type: 'string', + format: 'at-uri', + }, + }, + }, + }, + }, ComAtprotoRepoListRecords: { lexicon: 1, id: 'com.atproto.repo.listRecords', @@ -2925,6 +3125,75 @@ export const schemaDict = { }, }, }, + ComAtprotoServerActivateAccount: { + lexicon: 1, + id: 'com.atproto.server.activateAccount', + defs: { + main: { + type: 'procedure', + description: + "Activates a currently deactivated account. Used to finalize account migration after the account's repo is imported and identity is setup.", + }, + }, + }, + ComAtprotoServerCheckAccountStatus: { + lexicon: 1, + id: 'com.atproto.server.checkAccountStatus', + defs: { + main: { + type: 'query', + description: + 'Returns the status of an account, especially as pertaining to import or recovery. Can be called many times over the course of an account migration. Requires auth and can only be called pertaining to oneself.', + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: [ + 'activated', + 'validDid', + 'repoCommit', + 'repoRev', + 'repoBlocks', + 'indexedRecords', + 'privateStateValues', + 'expectedBlobs', + 'importedBlobs', + ], + properties: { + activated: { + type: 'boolean', + }, + validDid: { + type: 'boolean', + }, + repoCommit: { + type: 'string', + format: 'cid', + }, + repoRev: { + type: 'string', + }, + repoBlocks: { + type: 'integer', + }, + indexedRecords: { + type: 'integer', + }, + privateStateValues: { + type: 'integer', + }, + expectedBlobs: { + type: 'integer', + }, + importedBlobs: { + type: 'integer', + }, + }, + }, + }, + }, + }, + }, ComAtprotoServerConfirmEmail: { lexicon: 1, id: 'com.atproto.server.confirmEmail', @@ -3293,6 +3562,31 @@ export const schemaDict = { }, }, }, + ComAtprotoServerDeactivateAccount: { + lexicon: 1, + id: 'com.atproto.server.deactivateAccount', + defs: { + main: { + type: 'procedure', + description: + 'Deactivates a currently active account. Stops serving of repo, and future writes to repo until reactivated. Used to finalize account migration with the old host after the account has been activated on the new host.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + properties: { + deleteAfter: { + type: 'string', + format: 'datetime', + description: + 'A recommendation to server as to how long they should hold onto the deactivated account before deleting.', + }, + }, + }, + }, + }, + }, + }, ComAtprotoServerDefs: { lexicon: 1, id: 'com.atproto.server.defs', @@ -3413,7 +3707,7 @@ export const schemaDict = { encoding: 'application/json', schema: { type: 'object', - required: ['availableUserDomains'], + required: ['did', 'availableUserDomains'], properties: { inviteCodeRequired: { type: 'boolean', @@ -3438,6 +3732,10 @@ export const schemaDict = { description: 'URLs of service policy documents.', ref: 'lex:com.atproto.server.describeServer#links', }, + did: { + type: 'string', + format: 'did', + }, }, }, }, @@ -3502,6 +3800,41 @@ export const schemaDict = { }, }, }, + ComAtprotoServerGetServiceAuth: { + lexicon: 1, + id: 'com.atproto.server.getServiceAuth', + defs: { + main: { + type: 'query', + description: + 'Get a signed token on behalf of the requesting DID for the requested service.', + parameters: { + type: 'params', + required: ['aud'], + properties: { + aud: { + type: 'string', + format: 'did', + description: + 'The DID of the service that the token will be used to authenticate with', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['token'], + properties: { + token: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, ComAtprotoServerGetSession: { lexicon: 1, id: 'com.atproto.server.getSession', @@ -8582,7 +8915,14 @@ export const ids = { ComAtprotoAdminUpdateCommunicationTemplate: 'com.atproto.admin.updateCommunicationTemplate', ComAtprotoAdminUpdateSubjectStatus: 'com.atproto.admin.updateSubjectStatus', + ComAtprotoIdentityGetRecommendedDidCredentials: + 'com.atproto.identity.getRecommendedDidCredentials', + ComAtprotoIdentityRequestPlcOperationSignature: + 'com.atproto.identity.requestPlcOperationSignature', ComAtprotoIdentityResolveHandle: 'com.atproto.identity.resolveHandle', + ComAtprotoIdentitySignPlcOperation: 'com.atproto.identity.signPlcOperation', + ComAtprotoIdentitySubmitPlcOperation: + 'com.atproto.identity.submitPlcOperation', ComAtprotoIdentityUpdateHandle: 'com.atproto.identity.updateHandle', ComAtprotoLabelDefs: 'com.atproto.label.defs', ComAtprotoLabelQueryLabels: 'com.atproto.label.queryLabels', @@ -8594,22 +8934,28 @@ export const ids = { ComAtprotoRepoDeleteRecord: 'com.atproto.repo.deleteRecord', ComAtprotoRepoDescribeRepo: 'com.atproto.repo.describeRepo', ComAtprotoRepoGetRecord: 'com.atproto.repo.getRecord', + ComAtprotoRepoImportRepo: 'com.atproto.repo.importRepo', + ComAtprotoRepoListMissingBlobs: 'com.atproto.repo.listMissingBlobs', ComAtprotoRepoListRecords: 'com.atproto.repo.listRecords', ComAtprotoRepoPutRecord: 'com.atproto.repo.putRecord', ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef', ComAtprotoRepoUploadBlob: 'com.atproto.repo.uploadBlob', + ComAtprotoServerActivateAccount: 'com.atproto.server.activateAccount', + ComAtprotoServerCheckAccountStatus: 'com.atproto.server.checkAccountStatus', ComAtprotoServerConfirmEmail: 'com.atproto.server.confirmEmail', ComAtprotoServerCreateAccount: 'com.atproto.server.createAccount', ComAtprotoServerCreateAppPassword: 'com.atproto.server.createAppPassword', ComAtprotoServerCreateInviteCode: 'com.atproto.server.createInviteCode', ComAtprotoServerCreateInviteCodes: 'com.atproto.server.createInviteCodes', ComAtprotoServerCreateSession: 'com.atproto.server.createSession', + ComAtprotoServerDeactivateAccount: 'com.atproto.server.deactivateAccount', ComAtprotoServerDefs: 'com.atproto.server.defs', ComAtprotoServerDeleteAccount: 'com.atproto.server.deleteAccount', ComAtprotoServerDeleteSession: 'com.atproto.server.deleteSession', ComAtprotoServerDescribeServer: 'com.atproto.server.describeServer', ComAtprotoServerGetAccountInviteCodes: 'com.atproto.server.getAccountInviteCodes', + ComAtprotoServerGetServiceAuth: 'com.atproto.server.getServiceAuth', ComAtprotoServerGetSession: 'com.atproto.server.getSession', ComAtprotoServerListAppPasswords: 'com.atproto.server.listAppPasswords', ComAtprotoServerRefreshSession: 'com.atproto.server.refreshSession', diff --git a/packages/api/src/client/types/com/atproto/identity/getRecommendedDidCredentials.ts b/packages/api/src/client/types/com/atproto/identity/getRecommendedDidCredentials.ts new file mode 100644 index 00000000000..dfa143e4ab3 --- /dev/null +++ b/packages/api/src/client/types/com/atproto/identity/getRecommendedDidCredentials.ts @@ -0,0 +1,37 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' + +export interface QueryParams {} + +export type InputSchema = undefined + +export interface OutputSchema { + /** Recommended rotation keys for PLC dids. Should be undefined (or ignored) for did:webs. */ + rotationKeys?: string[] + alsoKnownAs?: string[] + verificationMethods?: {} + services?: {} + [k: string]: unknown +} + +export interface CallOptions { + headers?: Headers +} + +export interface Response { + success: boolean + headers: Headers + data: OutputSchema +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + } + return e +} diff --git a/packages/api/src/client/types/com/atproto/identity/requestPlcOperationSignature.ts b/packages/api/src/client/types/com/atproto/identity/requestPlcOperationSignature.ts new file mode 100644 index 00000000000..ef2ed1ac47c --- /dev/null +++ b/packages/api/src/client/types/com/atproto/identity/requestPlcOperationSignature.ts @@ -0,0 +1,28 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' + +export interface QueryParams {} + +export type InputSchema = undefined + +export interface CallOptions { + headers?: Headers + qp?: QueryParams +} + +export interface Response { + success: boolean + headers: Headers +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + } + return e +} diff --git a/packages/api/src/client/types/com/atproto/identity/signPlcOperation.ts b/packages/api/src/client/types/com/atproto/identity/signPlcOperation.ts new file mode 100644 index 00000000000..3060c1e3f4d --- /dev/null +++ b/packages/api/src/client/types/com/atproto/identity/signPlcOperation.ts @@ -0,0 +1,44 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' + +export interface QueryParams {} + +export interface InputSchema { + /** A token received through com.atproto.identity.requestPlcOperationSignature */ + token?: string + rotationKeys?: string[] + alsoKnownAs?: string[] + verificationMethods?: {} + services?: {} + [k: string]: unknown +} + +export interface OutputSchema { + /** A signed DID PLC operation. */ + operation: {} + [k: string]: unknown +} + +export interface CallOptions { + headers?: Headers + qp?: QueryParams + encoding: 'application/json' +} + +export interface Response { + success: boolean + headers: Headers + data: OutputSchema +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + } + return e +} diff --git a/packages/api/src/client/types/com/atproto/identity/submitPlcOperation.ts b/packages/api/src/client/types/com/atproto/identity/submitPlcOperation.ts new file mode 100644 index 00000000000..4ba52f74fc7 --- /dev/null +++ b/packages/api/src/client/types/com/atproto/identity/submitPlcOperation.ts @@ -0,0 +1,32 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' + +export interface QueryParams {} + +export interface InputSchema { + operation: {} + [k: string]: unknown +} + +export interface CallOptions { + headers?: Headers + qp?: QueryParams + encoding: 'application/json' +} + +export interface Response { + success: boolean + headers: Headers +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + } + return e +} diff --git a/packages/api/src/client/types/com/atproto/repo/importRepo.ts b/packages/api/src/client/types/com/atproto/repo/importRepo.ts new file mode 100644 index 00000000000..040cca671bf --- /dev/null +++ b/packages/api/src/client/types/com/atproto/repo/importRepo.ts @@ -0,0 +1,29 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' + +export interface QueryParams {} + +export type InputSchema = string | Uint8Array + +export interface CallOptions { + headers?: Headers + qp?: QueryParams + encoding: 'application/vnd.ipld.car' +} + +export interface Response { + success: boolean + headers: Headers +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + } + return e +} diff --git a/packages/api/src/client/types/com/atproto/repo/listMissingBlobs.ts b/packages/api/src/client/types/com/atproto/repo/listMissingBlobs.ts new file mode 100644 index 00000000000..b66f617eea7 --- /dev/null +++ b/packages/api/src/client/types/com/atproto/repo/listMissingBlobs.ts @@ -0,0 +1,55 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' + +export interface QueryParams { + limit?: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + blobs: RecordBlob[] + [k: string]: unknown +} + +export interface CallOptions { + headers?: Headers +} + +export interface Response { + success: boolean + headers: Headers + data: OutputSchema +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + } + return e +} + +export interface RecordBlob { + cid: string + recordUri: string + [k: string]: unknown +} + +export function isRecordBlob(v: unknown): v is RecordBlob { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'com.atproto.repo.listMissingBlobs#recordBlob' + ) +} + +export function validateRecordBlob(v: unknown): ValidationResult { + return lexicons.validate('com.atproto.repo.listMissingBlobs#recordBlob', v) +} diff --git a/packages/api/src/client/types/com/atproto/server/activateAccount.ts b/packages/api/src/client/types/com/atproto/server/activateAccount.ts new file mode 100644 index 00000000000..ef2ed1ac47c --- /dev/null +++ b/packages/api/src/client/types/com/atproto/server/activateAccount.ts @@ -0,0 +1,28 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' + +export interface QueryParams {} + +export type InputSchema = undefined + +export interface CallOptions { + headers?: Headers + qp?: QueryParams +} + +export interface Response { + success: boolean + headers: Headers +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + } + return e +} diff --git a/packages/api/src/client/types/com/atproto/server/checkAccountStatus.ts b/packages/api/src/client/types/com/atproto/server/checkAccountStatus.ts new file mode 100644 index 00000000000..86a942f81f3 --- /dev/null +++ b/packages/api/src/client/types/com/atproto/server/checkAccountStatus.ts @@ -0,0 +1,41 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' + +export interface QueryParams {} + +export type InputSchema = undefined + +export interface OutputSchema { + activated: boolean + validDid: boolean + repoCommit: string + repoRev: string + repoBlocks: number + indexedRecords: number + privateStateValues: number + expectedBlobs: number + importedBlobs: number + [k: string]: unknown +} + +export interface CallOptions { + headers?: Headers +} + +export interface Response { + success: boolean + headers: Headers + data: OutputSchema +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + } + return e +} diff --git a/packages/api/src/client/types/com/atproto/server/deactivateAccount.ts b/packages/api/src/client/types/com/atproto/server/deactivateAccount.ts new file mode 100644 index 00000000000..c88bd548243 --- /dev/null +++ b/packages/api/src/client/types/com/atproto/server/deactivateAccount.ts @@ -0,0 +1,33 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' + +export interface QueryParams {} + +export interface InputSchema { + /** A recommendation to server as to how long they should hold onto the deactivated account before deleting. */ + deleteAfter?: string + [k: string]: unknown +} + +export interface CallOptions { + headers?: Headers + qp?: QueryParams + encoding: 'application/json' +} + +export interface Response { + success: boolean + headers: Headers +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + } + return e +} diff --git a/packages/api/src/client/types/com/atproto/server/describeServer.ts b/packages/api/src/client/types/com/atproto/server/describeServer.ts index 5aca005ec9f..c4b749c7ada 100644 --- a/packages/api/src/client/types/com/atproto/server/describeServer.ts +++ b/packages/api/src/client/types/com/atproto/server/describeServer.ts @@ -19,6 +19,7 @@ export interface OutputSchema { /** List of domain suffixes that can be used in account handles. */ availableUserDomains: string[] links?: Links + did: string [k: string]: unknown } diff --git a/packages/api/src/client/types/com/atproto/server/getServiceAuth.ts b/packages/api/src/client/types/com/atproto/server/getServiceAuth.ts new file mode 100644 index 00000000000..6056960effc --- /dev/null +++ b/packages/api/src/client/types/com/atproto/server/getServiceAuth.ts @@ -0,0 +1,36 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' + +export interface QueryParams { + /** The DID of the service that the token will be used to authenticate with */ + aud: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + token: string + [k: string]: unknown +} + +export interface CallOptions { + headers?: Headers +} + +export interface Response { + success: boolean + headers: Headers + data: OutputSchema +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + } + return e +} diff --git a/packages/bsky/src/lexicon/index.ts b/packages/bsky/src/lexicon/index.ts index ab207718006..ed08ff55702 100644 --- a/packages/bsky/src/lexicon/index.ts +++ b/packages/bsky/src/lexicon/index.ts @@ -32,7 +32,11 @@ import * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/up import * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle' import * as ComAtprotoAdminUpdateCommunicationTemplate from './types/com/atproto/admin/updateCommunicationTemplate' import * as ComAtprotoAdminUpdateSubjectStatus from './types/com/atproto/admin/updateSubjectStatus' +import * as ComAtprotoIdentityGetRecommendedDidCredentials from './types/com/atproto/identity/getRecommendedDidCredentials' +import * as ComAtprotoIdentityRequestPlcOperationSignature from './types/com/atproto/identity/requestPlcOperationSignature' import * as ComAtprotoIdentityResolveHandle from './types/com/atproto/identity/resolveHandle' +import * as ComAtprotoIdentitySignPlcOperation from './types/com/atproto/identity/signPlcOperation' +import * as ComAtprotoIdentitySubmitPlcOperation from './types/com/atproto/identity/submitPlcOperation' import * as ComAtprotoIdentityUpdateHandle from './types/com/atproto/identity/updateHandle' import * as ComAtprotoLabelQueryLabels from './types/com/atproto/label/queryLabels' import * as ComAtprotoLabelSubscribeLabels from './types/com/atproto/label/subscribeLabels' @@ -42,19 +46,25 @@ import * as ComAtprotoRepoCreateRecord from './types/com/atproto/repo/createReco import * as ComAtprotoRepoDeleteRecord from './types/com/atproto/repo/deleteRecord' import * as ComAtprotoRepoDescribeRepo from './types/com/atproto/repo/describeRepo' import * as ComAtprotoRepoGetRecord from './types/com/atproto/repo/getRecord' +import * as ComAtprotoRepoImportRepo from './types/com/atproto/repo/importRepo' +import * as ComAtprotoRepoListMissingBlobs from './types/com/atproto/repo/listMissingBlobs' import * as ComAtprotoRepoListRecords from './types/com/atproto/repo/listRecords' import * as ComAtprotoRepoPutRecord from './types/com/atproto/repo/putRecord' import * as ComAtprotoRepoUploadBlob from './types/com/atproto/repo/uploadBlob' +import * as ComAtprotoServerActivateAccount from './types/com/atproto/server/activateAccount' +import * as ComAtprotoServerCheckAccountStatus from './types/com/atproto/server/checkAccountStatus' import * as ComAtprotoServerConfirmEmail from './types/com/atproto/server/confirmEmail' import * as ComAtprotoServerCreateAccount from './types/com/atproto/server/createAccount' import * as ComAtprotoServerCreateAppPassword from './types/com/atproto/server/createAppPassword' import * as ComAtprotoServerCreateInviteCode from './types/com/atproto/server/createInviteCode' import * as ComAtprotoServerCreateInviteCodes from './types/com/atproto/server/createInviteCodes' import * as ComAtprotoServerCreateSession from './types/com/atproto/server/createSession' +import * as ComAtprotoServerDeactivateAccount from './types/com/atproto/server/deactivateAccount' import * as ComAtprotoServerDeleteAccount from './types/com/atproto/server/deleteAccount' import * as ComAtprotoServerDeleteSession from './types/com/atproto/server/deleteSession' import * as ComAtprotoServerDescribeServer from './types/com/atproto/server/describeServer' import * as ComAtprotoServerGetAccountInviteCodes from './types/com/atproto/server/getAccountInviteCodes' +import * as ComAtprotoServerGetServiceAuth from './types/com/atproto/server/getServiceAuth' import * as ComAtprotoServerGetSession from './types/com/atproto/server/getSession' import * as ComAtprotoServerListAppPasswords from './types/com/atproto/server/listAppPasswords' import * as ComAtprotoServerRefreshSession from './types/com/atproto/server/refreshSession' @@ -467,6 +477,32 @@ export class ComAtprotoIdentityNS { this._server = server } + getRecommendedDidCredentials( + cfg: ConfigOf< + AV, + ComAtprotoIdentityGetRecommendedDidCredentials.Handler>, + ComAtprotoIdentityGetRecommendedDidCredentials.HandlerReqCtx< + ExtractAuth + > + >, + ) { + const nsid = 'com.atproto.identity.getRecommendedDidCredentials' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + + requestPlcOperationSignature( + cfg: ConfigOf< + AV, + ComAtprotoIdentityRequestPlcOperationSignature.Handler>, + ComAtprotoIdentityRequestPlcOperationSignature.HandlerReqCtx< + ExtractAuth + > + >, + ) { + const nsid = 'com.atproto.identity.requestPlcOperationSignature' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + resolveHandle( cfg: ConfigOf< AV, @@ -478,6 +514,28 @@ export class ComAtprotoIdentityNS { return this._server.xrpc.method(nsid, cfg) } + signPlcOperation( + cfg: ConfigOf< + AV, + ComAtprotoIdentitySignPlcOperation.Handler>, + ComAtprotoIdentitySignPlcOperation.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.identity.signPlcOperation' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + + submitPlcOperation( + cfg: ConfigOf< + AV, + ComAtprotoIdentitySubmitPlcOperation.Handler>, + ComAtprotoIdentitySubmitPlcOperation.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.identity.submitPlcOperation' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + updateHandle( cfg: ConfigOf< AV, @@ -601,6 +659,28 @@ export class ComAtprotoRepoNS { return this._server.xrpc.method(nsid, cfg) } + importRepo( + cfg: ConfigOf< + AV, + ComAtprotoRepoImportRepo.Handler>, + ComAtprotoRepoImportRepo.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.repo.importRepo' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + + listMissingBlobs( + cfg: ConfigOf< + AV, + ComAtprotoRepoListMissingBlobs.Handler>, + ComAtprotoRepoListMissingBlobs.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.repo.listMissingBlobs' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + listRecords( cfg: ConfigOf< AV, @@ -642,6 +722,28 @@ export class ComAtprotoServerNS { this._server = server } + activateAccount( + cfg: ConfigOf< + AV, + ComAtprotoServerActivateAccount.Handler>, + ComAtprotoServerActivateAccount.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.server.activateAccount' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + + checkAccountStatus( + cfg: ConfigOf< + AV, + ComAtprotoServerCheckAccountStatus.Handler>, + ComAtprotoServerCheckAccountStatus.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.server.checkAccountStatus' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + confirmEmail( cfg: ConfigOf< AV, @@ -708,6 +810,17 @@ export class ComAtprotoServerNS { return this._server.xrpc.method(nsid, cfg) } + deactivateAccount( + cfg: ConfigOf< + AV, + ComAtprotoServerDeactivateAccount.Handler>, + ComAtprotoServerDeactivateAccount.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.server.deactivateAccount' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + deleteAccount( cfg: ConfigOf< AV, @@ -752,6 +865,17 @@ export class ComAtprotoServerNS { return this._server.xrpc.method(nsid, cfg) } + getServiceAuth( + cfg: ConfigOf< + AV, + ComAtprotoServerGetServiceAuth.Handler>, + ComAtprotoServerGetServiceAuth.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.server.getServiceAuth' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + getSession( cfg: ConfigOf< AV, diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index 01a2f683d77..a3491462d50 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -1968,6 +1968,56 @@ export const schemaDict = { }, }, }, + ComAtprotoIdentityGetRecommendedDidCredentials: { + lexicon: 1, + id: 'com.atproto.identity.getRecommendedDidCredentials', + defs: { + main: { + type: 'query', + description: + 'Describe the credentials that should be included in the DID doc of an account that is migrating to this service.', + output: { + encoding: 'application/json', + schema: { + type: 'object', + properties: { + rotationKeys: { + description: + 'Recommended rotation keys for PLC dids. Should be undefined (or ignored) for did:webs.', + type: 'array', + items: { + type: 'string', + }, + }, + alsoKnownAs: { + type: 'array', + items: { + type: 'string', + }, + }, + verificationMethods: { + type: 'unknown', + }, + services: { + type: 'unknown', + }, + }, + }, + }, + }, + }, + }, + ComAtprotoIdentityRequestPlcOperationSignature: { + lexicon: 1, + id: 'com.atproto.identity.requestPlcOperationSignature', + defs: { + main: { + type: 'procedure', + description: + 'Request an email with a code to in order to request a signed PLC operation. Requires Auth.', + }, + }, + }, ComAtprotoIdentityResolveHandle: { lexicon: 1, id: 'com.atproto.identity.resolveHandle', @@ -2002,6 +2052,84 @@ export const schemaDict = { }, }, }, + ComAtprotoIdentitySignPlcOperation: { + lexicon: 1, + id: 'com.atproto.identity.signPlcOperation', + defs: { + main: { + type: 'procedure', + description: + "Signs a PLC operation to update some value(s) in the requesting DID's document.", + input: { + encoding: 'application/json', + schema: { + type: 'object', + properties: { + token: { + description: + 'A token received through com.atproto.identity.requestPlcOperationSignature', + type: 'string', + }, + rotationKeys: { + type: 'array', + items: { + type: 'string', + }, + }, + alsoKnownAs: { + type: 'array', + items: { + type: 'string', + }, + }, + verificationMethods: { + type: 'unknown', + }, + services: { + type: 'unknown', + }, + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['operation'], + properties: { + operation: { + type: 'unknown', + description: 'A signed DID PLC operation.', + }, + }, + }, + }, + }, + }, + }, + ComAtprotoIdentitySubmitPlcOperation: { + lexicon: 1, + id: 'com.atproto.identity.submitPlcOperation', + defs: { + main: { + type: 'procedure', + description: + "Validates a PLC operation to ensure that it doesn't violate a service's constraints or get the identity into a bad state, then submits it to the PLC registry", + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['operation'], + properties: { + operation: { + type: 'unknown', + }, + }, + }, + }, + }, + }, + }, ComAtprotoIdentityUpdateHandle: { lexicon: 1, id: 'com.atproto.identity.updateHandle', @@ -2710,6 +2838,78 @@ export const schemaDict = { }, }, }, + ComAtprotoRepoImportRepo: { + lexicon: 1, + id: 'com.atproto.repo.importRepo', + defs: { + main: { + type: 'procedure', + description: + 'Import a repo in the form of a CAR file. Requires Content-Length HTTP header to be set.', + input: { + encoding: 'application/vnd.ipld.car', + }, + }, + }, + }, + ComAtprotoRepoListMissingBlobs: { + lexicon: 1, + id: 'com.atproto.repo.listMissingBlobs', + defs: { + main: { + type: 'query', + description: + 'Returns a list of missing blobs for the requesting account. Intended to be used in the account migration flow.', + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 1000, + default: 500, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['blobs'], + properties: { + cursor: { + type: 'string', + }, + blobs: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:com.atproto.repo.listMissingBlobs#recordBlob', + }, + }, + }, + }, + }, + }, + recordBlob: { + type: 'object', + required: ['cid', 'recordUri'], + properties: { + cid: { + type: 'string', + format: 'cid', + }, + recordUri: { + type: 'string', + format: 'at-uri', + }, + }, + }, + }, + }, ComAtprotoRepoListRecords: { lexicon: 1, id: 'com.atproto.repo.listRecords', @@ -2925,6 +3125,75 @@ export const schemaDict = { }, }, }, + ComAtprotoServerActivateAccount: { + lexicon: 1, + id: 'com.atproto.server.activateAccount', + defs: { + main: { + type: 'procedure', + description: + "Activates a currently deactivated account. Used to finalize account migration after the account's repo is imported and identity is setup.", + }, + }, + }, + ComAtprotoServerCheckAccountStatus: { + lexicon: 1, + id: 'com.atproto.server.checkAccountStatus', + defs: { + main: { + type: 'query', + description: + 'Returns the status of an account, especially as pertaining to import or recovery. Can be called many times over the course of an account migration. Requires auth and can only be called pertaining to oneself.', + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: [ + 'activated', + 'validDid', + 'repoCommit', + 'repoRev', + 'repoBlocks', + 'indexedRecords', + 'privateStateValues', + 'expectedBlobs', + 'importedBlobs', + ], + properties: { + activated: { + type: 'boolean', + }, + validDid: { + type: 'boolean', + }, + repoCommit: { + type: 'string', + format: 'cid', + }, + repoRev: { + type: 'string', + }, + repoBlocks: { + type: 'integer', + }, + indexedRecords: { + type: 'integer', + }, + privateStateValues: { + type: 'integer', + }, + expectedBlobs: { + type: 'integer', + }, + importedBlobs: { + type: 'integer', + }, + }, + }, + }, + }, + }, + }, ComAtprotoServerConfirmEmail: { lexicon: 1, id: 'com.atproto.server.confirmEmail', @@ -3293,6 +3562,31 @@ export const schemaDict = { }, }, }, + ComAtprotoServerDeactivateAccount: { + lexicon: 1, + id: 'com.atproto.server.deactivateAccount', + defs: { + main: { + type: 'procedure', + description: + 'Deactivates a currently active account. Stops serving of repo, and future writes to repo until reactivated. Used to finalize account migration with the old host after the account has been activated on the new host.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + properties: { + deleteAfter: { + type: 'string', + format: 'datetime', + description: + 'A recommendation to server as to how long they should hold onto the deactivated account before deleting.', + }, + }, + }, + }, + }, + }, + }, ComAtprotoServerDefs: { lexicon: 1, id: 'com.atproto.server.defs', @@ -3413,7 +3707,7 @@ export const schemaDict = { encoding: 'application/json', schema: { type: 'object', - required: ['availableUserDomains'], + required: ['did', 'availableUserDomains'], properties: { inviteCodeRequired: { type: 'boolean', @@ -3438,6 +3732,10 @@ export const schemaDict = { description: 'URLs of service policy documents.', ref: 'lex:com.atproto.server.describeServer#links', }, + did: { + type: 'string', + format: 'did', + }, }, }, }, @@ -3502,6 +3800,41 @@ export const schemaDict = { }, }, }, + ComAtprotoServerGetServiceAuth: { + lexicon: 1, + id: 'com.atproto.server.getServiceAuth', + defs: { + main: { + type: 'query', + description: + 'Get a signed token on behalf of the requesting DID for the requested service.', + parameters: { + type: 'params', + required: ['aud'], + properties: { + aud: { + type: 'string', + format: 'did', + description: + 'The DID of the service that the token will be used to authenticate with', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['token'], + properties: { + token: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, ComAtprotoServerGetSession: { lexicon: 1, id: 'com.atproto.server.getSession', @@ -8582,7 +8915,14 @@ export const ids = { ComAtprotoAdminUpdateCommunicationTemplate: 'com.atproto.admin.updateCommunicationTemplate', ComAtprotoAdminUpdateSubjectStatus: 'com.atproto.admin.updateSubjectStatus', + ComAtprotoIdentityGetRecommendedDidCredentials: + 'com.atproto.identity.getRecommendedDidCredentials', + ComAtprotoIdentityRequestPlcOperationSignature: + 'com.atproto.identity.requestPlcOperationSignature', ComAtprotoIdentityResolveHandle: 'com.atproto.identity.resolveHandle', + ComAtprotoIdentitySignPlcOperation: 'com.atproto.identity.signPlcOperation', + ComAtprotoIdentitySubmitPlcOperation: + 'com.atproto.identity.submitPlcOperation', ComAtprotoIdentityUpdateHandle: 'com.atproto.identity.updateHandle', ComAtprotoLabelDefs: 'com.atproto.label.defs', ComAtprotoLabelQueryLabels: 'com.atproto.label.queryLabels', @@ -8594,22 +8934,28 @@ export const ids = { ComAtprotoRepoDeleteRecord: 'com.atproto.repo.deleteRecord', ComAtprotoRepoDescribeRepo: 'com.atproto.repo.describeRepo', ComAtprotoRepoGetRecord: 'com.atproto.repo.getRecord', + ComAtprotoRepoImportRepo: 'com.atproto.repo.importRepo', + ComAtprotoRepoListMissingBlobs: 'com.atproto.repo.listMissingBlobs', ComAtprotoRepoListRecords: 'com.atproto.repo.listRecords', ComAtprotoRepoPutRecord: 'com.atproto.repo.putRecord', ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef', ComAtprotoRepoUploadBlob: 'com.atproto.repo.uploadBlob', + ComAtprotoServerActivateAccount: 'com.atproto.server.activateAccount', + ComAtprotoServerCheckAccountStatus: 'com.atproto.server.checkAccountStatus', ComAtprotoServerConfirmEmail: 'com.atproto.server.confirmEmail', ComAtprotoServerCreateAccount: 'com.atproto.server.createAccount', ComAtprotoServerCreateAppPassword: 'com.atproto.server.createAppPassword', ComAtprotoServerCreateInviteCode: 'com.atproto.server.createInviteCode', ComAtprotoServerCreateInviteCodes: 'com.atproto.server.createInviteCodes', ComAtprotoServerCreateSession: 'com.atproto.server.createSession', + ComAtprotoServerDeactivateAccount: 'com.atproto.server.deactivateAccount', ComAtprotoServerDefs: 'com.atproto.server.defs', ComAtprotoServerDeleteAccount: 'com.atproto.server.deleteAccount', ComAtprotoServerDeleteSession: 'com.atproto.server.deleteSession', ComAtprotoServerDescribeServer: 'com.atproto.server.describeServer', ComAtprotoServerGetAccountInviteCodes: 'com.atproto.server.getAccountInviteCodes', + ComAtprotoServerGetServiceAuth: 'com.atproto.server.getServiceAuth', ComAtprotoServerGetSession: 'com.atproto.server.getSession', ComAtprotoServerListAppPasswords: 'com.atproto.server.listAppPasswords', ComAtprotoServerRefreshSession: 'com.atproto.server.refreshSession', diff --git a/packages/bsky/src/lexicon/types/com/atproto/identity/getRecommendedDidCredentials.ts b/packages/bsky/src/lexicon/types/com/atproto/identity/getRecommendedDidCredentials.ts new file mode 100644 index 00000000000..5fa374de737 --- /dev/null +++ b/packages/bsky/src/lexicon/types/com/atproto/identity/getRecommendedDidCredentials.ts @@ -0,0 +1,47 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export type InputSchema = undefined + +export interface OutputSchema { + /** Recommended rotation keys for PLC dids. Should be undefined (or ignored) for did:webs. */ + rotationKeys?: string[] + alsoKnownAs?: string[] + verificationMethods?: {} + services?: {} + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/identity/requestPlcOperationSignature.ts b/packages/bsky/src/lexicon/types/com/atproto/identity/requestPlcOperationSignature.ts new file mode 100644 index 00000000000..82672f1d1c7 --- /dev/null +++ b/packages/bsky/src/lexicon/types/com/atproto/identity/requestPlcOperationSignature.ts @@ -0,0 +1,31 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export type InputSchema = undefined +export type HandlerInput = undefined + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/identity/signPlcOperation.ts b/packages/bsky/src/lexicon/types/com/atproto/identity/signPlcOperation.ts new file mode 100644 index 00000000000..3c908c049f2 --- /dev/null +++ b/packages/bsky/src/lexicon/types/com/atproto/identity/signPlcOperation.ts @@ -0,0 +1,55 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + /** A token received through com.atproto.identity.requestPlcOperationSignature */ + token?: string + rotationKeys?: string[] + alsoKnownAs?: string[] + verificationMethods?: {} + services?: {} + [k: string]: unknown +} + +export interface OutputSchema { + /** A signed DID PLC operation. */ + operation: {} + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/identity/submitPlcOperation.ts b/packages/bsky/src/lexicon/types/com/atproto/identity/submitPlcOperation.ts new file mode 100644 index 00000000000..5290b55d023 --- /dev/null +++ b/packages/bsky/src/lexicon/types/com/atproto/identity/submitPlcOperation.ts @@ -0,0 +1,38 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + operation: {} + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/importRepo.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/importRepo.ts new file mode 100644 index 00000000000..921798c0ded --- /dev/null +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/importRepo.ts @@ -0,0 +1,36 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import stream from 'stream' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export type InputSchema = string | Uint8Array + +export interface HandlerInput { + encoding: 'application/vnd.ipld.car' + body: stream.Readable +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/repo/listMissingBlobs.ts b/packages/bsky/src/lexicon/types/com/atproto/repo/listMissingBlobs.ts new file mode 100644 index 00000000000..40f4d385e47 --- /dev/null +++ b/packages/bsky/src/lexicon/types/com/atproto/repo/listMissingBlobs.ts @@ -0,0 +1,65 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams { + limit: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + blobs: RecordBlob[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput + +export interface RecordBlob { + cid: string + recordUri: string + [k: string]: unknown +} + +export function isRecordBlob(v: unknown): v is RecordBlob { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'com.atproto.repo.listMissingBlobs#recordBlob' + ) +} + +export function validateRecordBlob(v: unknown): ValidationResult { + return lexicons.validate('com.atproto.repo.listMissingBlobs#recordBlob', v) +} diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/activateAccount.ts b/packages/bsky/src/lexicon/types/com/atproto/server/activateAccount.ts new file mode 100644 index 00000000000..82672f1d1c7 --- /dev/null +++ b/packages/bsky/src/lexicon/types/com/atproto/server/activateAccount.ts @@ -0,0 +1,31 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export type InputSchema = undefined +export type HandlerInput = undefined + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/checkAccountStatus.ts b/packages/bsky/src/lexicon/types/com/atproto/server/checkAccountStatus.ts new file mode 100644 index 00000000000..f17182a8dce --- /dev/null +++ b/packages/bsky/src/lexicon/types/com/atproto/server/checkAccountStatus.ts @@ -0,0 +1,51 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export type InputSchema = undefined + +export interface OutputSchema { + activated: boolean + validDid: boolean + repoCommit: string + repoRev: string + repoBlocks: number + indexedRecords: number + privateStateValues: number + expectedBlobs: number + importedBlobs: number + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/deactivateAccount.ts b/packages/bsky/src/lexicon/types/com/atproto/server/deactivateAccount.ts new file mode 100644 index 00000000000..b3793d6b2e0 --- /dev/null +++ b/packages/bsky/src/lexicon/types/com/atproto/server/deactivateAccount.ts @@ -0,0 +1,39 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + /** A recommendation to server as to how long they should hold onto the deactivated account before deleting. */ + deleteAfter?: string + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/describeServer.ts b/packages/bsky/src/lexicon/types/com/atproto/server/describeServer.ts index 0e64c9d9708..c2625347f20 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/server/describeServer.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/server/describeServer.ts @@ -20,6 +20,7 @@ export interface OutputSchema { /** List of domain suffixes that can be used in account handles. */ availableUserDomains: string[] links?: Links + did: string [k: string]: unknown } diff --git a/packages/bsky/src/lexicon/types/com/atproto/server/getServiceAuth.ts b/packages/bsky/src/lexicon/types/com/atproto/server/getServiceAuth.ts new file mode 100644 index 00000000000..73efe2313a9 --- /dev/null +++ b/packages/bsky/src/lexicon/types/com/atproto/server/getServiceAuth.ts @@ -0,0 +1,46 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams { + /** The DID of the service that the token will be used to authenticate with */ + aud: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + token: string + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/ozone/src/lexicon/index.ts b/packages/ozone/src/lexicon/index.ts index ab207718006..ed08ff55702 100644 --- a/packages/ozone/src/lexicon/index.ts +++ b/packages/ozone/src/lexicon/index.ts @@ -32,7 +32,11 @@ import * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/up import * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle' import * as ComAtprotoAdminUpdateCommunicationTemplate from './types/com/atproto/admin/updateCommunicationTemplate' import * as ComAtprotoAdminUpdateSubjectStatus from './types/com/atproto/admin/updateSubjectStatus' +import * as ComAtprotoIdentityGetRecommendedDidCredentials from './types/com/atproto/identity/getRecommendedDidCredentials' +import * as ComAtprotoIdentityRequestPlcOperationSignature from './types/com/atproto/identity/requestPlcOperationSignature' import * as ComAtprotoIdentityResolveHandle from './types/com/atproto/identity/resolveHandle' +import * as ComAtprotoIdentitySignPlcOperation from './types/com/atproto/identity/signPlcOperation' +import * as ComAtprotoIdentitySubmitPlcOperation from './types/com/atproto/identity/submitPlcOperation' import * as ComAtprotoIdentityUpdateHandle from './types/com/atproto/identity/updateHandle' import * as ComAtprotoLabelQueryLabels from './types/com/atproto/label/queryLabels' import * as ComAtprotoLabelSubscribeLabels from './types/com/atproto/label/subscribeLabels' @@ -42,19 +46,25 @@ import * as ComAtprotoRepoCreateRecord from './types/com/atproto/repo/createReco import * as ComAtprotoRepoDeleteRecord from './types/com/atproto/repo/deleteRecord' import * as ComAtprotoRepoDescribeRepo from './types/com/atproto/repo/describeRepo' import * as ComAtprotoRepoGetRecord from './types/com/atproto/repo/getRecord' +import * as ComAtprotoRepoImportRepo from './types/com/atproto/repo/importRepo' +import * as ComAtprotoRepoListMissingBlobs from './types/com/atproto/repo/listMissingBlobs' import * as ComAtprotoRepoListRecords from './types/com/atproto/repo/listRecords' import * as ComAtprotoRepoPutRecord from './types/com/atproto/repo/putRecord' import * as ComAtprotoRepoUploadBlob from './types/com/atproto/repo/uploadBlob' +import * as ComAtprotoServerActivateAccount from './types/com/atproto/server/activateAccount' +import * as ComAtprotoServerCheckAccountStatus from './types/com/atproto/server/checkAccountStatus' import * as ComAtprotoServerConfirmEmail from './types/com/atproto/server/confirmEmail' import * as ComAtprotoServerCreateAccount from './types/com/atproto/server/createAccount' import * as ComAtprotoServerCreateAppPassword from './types/com/atproto/server/createAppPassword' import * as ComAtprotoServerCreateInviteCode from './types/com/atproto/server/createInviteCode' import * as ComAtprotoServerCreateInviteCodes from './types/com/atproto/server/createInviteCodes' import * as ComAtprotoServerCreateSession from './types/com/atproto/server/createSession' +import * as ComAtprotoServerDeactivateAccount from './types/com/atproto/server/deactivateAccount' import * as ComAtprotoServerDeleteAccount from './types/com/atproto/server/deleteAccount' import * as ComAtprotoServerDeleteSession from './types/com/atproto/server/deleteSession' import * as ComAtprotoServerDescribeServer from './types/com/atproto/server/describeServer' import * as ComAtprotoServerGetAccountInviteCodes from './types/com/atproto/server/getAccountInviteCodes' +import * as ComAtprotoServerGetServiceAuth from './types/com/atproto/server/getServiceAuth' import * as ComAtprotoServerGetSession from './types/com/atproto/server/getSession' import * as ComAtprotoServerListAppPasswords from './types/com/atproto/server/listAppPasswords' import * as ComAtprotoServerRefreshSession from './types/com/atproto/server/refreshSession' @@ -467,6 +477,32 @@ export class ComAtprotoIdentityNS { this._server = server } + getRecommendedDidCredentials( + cfg: ConfigOf< + AV, + ComAtprotoIdentityGetRecommendedDidCredentials.Handler>, + ComAtprotoIdentityGetRecommendedDidCredentials.HandlerReqCtx< + ExtractAuth + > + >, + ) { + const nsid = 'com.atproto.identity.getRecommendedDidCredentials' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + + requestPlcOperationSignature( + cfg: ConfigOf< + AV, + ComAtprotoIdentityRequestPlcOperationSignature.Handler>, + ComAtprotoIdentityRequestPlcOperationSignature.HandlerReqCtx< + ExtractAuth + > + >, + ) { + const nsid = 'com.atproto.identity.requestPlcOperationSignature' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + resolveHandle( cfg: ConfigOf< AV, @@ -478,6 +514,28 @@ export class ComAtprotoIdentityNS { return this._server.xrpc.method(nsid, cfg) } + signPlcOperation( + cfg: ConfigOf< + AV, + ComAtprotoIdentitySignPlcOperation.Handler>, + ComAtprotoIdentitySignPlcOperation.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.identity.signPlcOperation' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + + submitPlcOperation( + cfg: ConfigOf< + AV, + ComAtprotoIdentitySubmitPlcOperation.Handler>, + ComAtprotoIdentitySubmitPlcOperation.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.identity.submitPlcOperation' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + updateHandle( cfg: ConfigOf< AV, @@ -601,6 +659,28 @@ export class ComAtprotoRepoNS { return this._server.xrpc.method(nsid, cfg) } + importRepo( + cfg: ConfigOf< + AV, + ComAtprotoRepoImportRepo.Handler>, + ComAtprotoRepoImportRepo.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.repo.importRepo' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + + listMissingBlobs( + cfg: ConfigOf< + AV, + ComAtprotoRepoListMissingBlobs.Handler>, + ComAtprotoRepoListMissingBlobs.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.repo.listMissingBlobs' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + listRecords( cfg: ConfigOf< AV, @@ -642,6 +722,28 @@ export class ComAtprotoServerNS { this._server = server } + activateAccount( + cfg: ConfigOf< + AV, + ComAtprotoServerActivateAccount.Handler>, + ComAtprotoServerActivateAccount.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.server.activateAccount' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + + checkAccountStatus( + cfg: ConfigOf< + AV, + ComAtprotoServerCheckAccountStatus.Handler>, + ComAtprotoServerCheckAccountStatus.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.server.checkAccountStatus' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + confirmEmail( cfg: ConfigOf< AV, @@ -708,6 +810,17 @@ export class ComAtprotoServerNS { return this._server.xrpc.method(nsid, cfg) } + deactivateAccount( + cfg: ConfigOf< + AV, + ComAtprotoServerDeactivateAccount.Handler>, + ComAtprotoServerDeactivateAccount.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.server.deactivateAccount' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + deleteAccount( cfg: ConfigOf< AV, @@ -752,6 +865,17 @@ export class ComAtprotoServerNS { return this._server.xrpc.method(nsid, cfg) } + getServiceAuth( + cfg: ConfigOf< + AV, + ComAtprotoServerGetServiceAuth.Handler>, + ComAtprotoServerGetServiceAuth.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.server.getServiceAuth' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + getSession( cfg: ConfigOf< AV, diff --git a/packages/ozone/src/lexicon/lexicons.ts b/packages/ozone/src/lexicon/lexicons.ts index 01a2f683d77..a3491462d50 100644 --- a/packages/ozone/src/lexicon/lexicons.ts +++ b/packages/ozone/src/lexicon/lexicons.ts @@ -1968,6 +1968,56 @@ export const schemaDict = { }, }, }, + ComAtprotoIdentityGetRecommendedDidCredentials: { + lexicon: 1, + id: 'com.atproto.identity.getRecommendedDidCredentials', + defs: { + main: { + type: 'query', + description: + 'Describe the credentials that should be included in the DID doc of an account that is migrating to this service.', + output: { + encoding: 'application/json', + schema: { + type: 'object', + properties: { + rotationKeys: { + description: + 'Recommended rotation keys for PLC dids. Should be undefined (or ignored) for did:webs.', + type: 'array', + items: { + type: 'string', + }, + }, + alsoKnownAs: { + type: 'array', + items: { + type: 'string', + }, + }, + verificationMethods: { + type: 'unknown', + }, + services: { + type: 'unknown', + }, + }, + }, + }, + }, + }, + }, + ComAtprotoIdentityRequestPlcOperationSignature: { + lexicon: 1, + id: 'com.atproto.identity.requestPlcOperationSignature', + defs: { + main: { + type: 'procedure', + description: + 'Request an email with a code to in order to request a signed PLC operation. Requires Auth.', + }, + }, + }, ComAtprotoIdentityResolveHandle: { lexicon: 1, id: 'com.atproto.identity.resolveHandle', @@ -2002,6 +2052,84 @@ export const schemaDict = { }, }, }, + ComAtprotoIdentitySignPlcOperation: { + lexicon: 1, + id: 'com.atproto.identity.signPlcOperation', + defs: { + main: { + type: 'procedure', + description: + "Signs a PLC operation to update some value(s) in the requesting DID's document.", + input: { + encoding: 'application/json', + schema: { + type: 'object', + properties: { + token: { + description: + 'A token received through com.atproto.identity.requestPlcOperationSignature', + type: 'string', + }, + rotationKeys: { + type: 'array', + items: { + type: 'string', + }, + }, + alsoKnownAs: { + type: 'array', + items: { + type: 'string', + }, + }, + verificationMethods: { + type: 'unknown', + }, + services: { + type: 'unknown', + }, + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['operation'], + properties: { + operation: { + type: 'unknown', + description: 'A signed DID PLC operation.', + }, + }, + }, + }, + }, + }, + }, + ComAtprotoIdentitySubmitPlcOperation: { + lexicon: 1, + id: 'com.atproto.identity.submitPlcOperation', + defs: { + main: { + type: 'procedure', + description: + "Validates a PLC operation to ensure that it doesn't violate a service's constraints or get the identity into a bad state, then submits it to the PLC registry", + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['operation'], + properties: { + operation: { + type: 'unknown', + }, + }, + }, + }, + }, + }, + }, ComAtprotoIdentityUpdateHandle: { lexicon: 1, id: 'com.atproto.identity.updateHandle', @@ -2710,6 +2838,78 @@ export const schemaDict = { }, }, }, + ComAtprotoRepoImportRepo: { + lexicon: 1, + id: 'com.atproto.repo.importRepo', + defs: { + main: { + type: 'procedure', + description: + 'Import a repo in the form of a CAR file. Requires Content-Length HTTP header to be set.', + input: { + encoding: 'application/vnd.ipld.car', + }, + }, + }, + }, + ComAtprotoRepoListMissingBlobs: { + lexicon: 1, + id: 'com.atproto.repo.listMissingBlobs', + defs: { + main: { + type: 'query', + description: + 'Returns a list of missing blobs for the requesting account. Intended to be used in the account migration flow.', + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 1000, + default: 500, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['blobs'], + properties: { + cursor: { + type: 'string', + }, + blobs: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:com.atproto.repo.listMissingBlobs#recordBlob', + }, + }, + }, + }, + }, + }, + recordBlob: { + type: 'object', + required: ['cid', 'recordUri'], + properties: { + cid: { + type: 'string', + format: 'cid', + }, + recordUri: { + type: 'string', + format: 'at-uri', + }, + }, + }, + }, + }, ComAtprotoRepoListRecords: { lexicon: 1, id: 'com.atproto.repo.listRecords', @@ -2925,6 +3125,75 @@ export const schemaDict = { }, }, }, + ComAtprotoServerActivateAccount: { + lexicon: 1, + id: 'com.atproto.server.activateAccount', + defs: { + main: { + type: 'procedure', + description: + "Activates a currently deactivated account. Used to finalize account migration after the account's repo is imported and identity is setup.", + }, + }, + }, + ComAtprotoServerCheckAccountStatus: { + lexicon: 1, + id: 'com.atproto.server.checkAccountStatus', + defs: { + main: { + type: 'query', + description: + 'Returns the status of an account, especially as pertaining to import or recovery. Can be called many times over the course of an account migration. Requires auth and can only be called pertaining to oneself.', + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: [ + 'activated', + 'validDid', + 'repoCommit', + 'repoRev', + 'repoBlocks', + 'indexedRecords', + 'privateStateValues', + 'expectedBlobs', + 'importedBlobs', + ], + properties: { + activated: { + type: 'boolean', + }, + validDid: { + type: 'boolean', + }, + repoCommit: { + type: 'string', + format: 'cid', + }, + repoRev: { + type: 'string', + }, + repoBlocks: { + type: 'integer', + }, + indexedRecords: { + type: 'integer', + }, + privateStateValues: { + type: 'integer', + }, + expectedBlobs: { + type: 'integer', + }, + importedBlobs: { + type: 'integer', + }, + }, + }, + }, + }, + }, + }, ComAtprotoServerConfirmEmail: { lexicon: 1, id: 'com.atproto.server.confirmEmail', @@ -3293,6 +3562,31 @@ export const schemaDict = { }, }, }, + ComAtprotoServerDeactivateAccount: { + lexicon: 1, + id: 'com.atproto.server.deactivateAccount', + defs: { + main: { + type: 'procedure', + description: + 'Deactivates a currently active account. Stops serving of repo, and future writes to repo until reactivated. Used to finalize account migration with the old host after the account has been activated on the new host.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + properties: { + deleteAfter: { + type: 'string', + format: 'datetime', + description: + 'A recommendation to server as to how long they should hold onto the deactivated account before deleting.', + }, + }, + }, + }, + }, + }, + }, ComAtprotoServerDefs: { lexicon: 1, id: 'com.atproto.server.defs', @@ -3413,7 +3707,7 @@ export const schemaDict = { encoding: 'application/json', schema: { type: 'object', - required: ['availableUserDomains'], + required: ['did', 'availableUserDomains'], properties: { inviteCodeRequired: { type: 'boolean', @@ -3438,6 +3732,10 @@ export const schemaDict = { description: 'URLs of service policy documents.', ref: 'lex:com.atproto.server.describeServer#links', }, + did: { + type: 'string', + format: 'did', + }, }, }, }, @@ -3502,6 +3800,41 @@ export const schemaDict = { }, }, }, + ComAtprotoServerGetServiceAuth: { + lexicon: 1, + id: 'com.atproto.server.getServiceAuth', + defs: { + main: { + type: 'query', + description: + 'Get a signed token on behalf of the requesting DID for the requested service.', + parameters: { + type: 'params', + required: ['aud'], + properties: { + aud: { + type: 'string', + format: 'did', + description: + 'The DID of the service that the token will be used to authenticate with', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['token'], + properties: { + token: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, ComAtprotoServerGetSession: { lexicon: 1, id: 'com.atproto.server.getSession', @@ -8582,7 +8915,14 @@ export const ids = { ComAtprotoAdminUpdateCommunicationTemplate: 'com.atproto.admin.updateCommunicationTemplate', ComAtprotoAdminUpdateSubjectStatus: 'com.atproto.admin.updateSubjectStatus', + ComAtprotoIdentityGetRecommendedDidCredentials: + 'com.atproto.identity.getRecommendedDidCredentials', + ComAtprotoIdentityRequestPlcOperationSignature: + 'com.atproto.identity.requestPlcOperationSignature', ComAtprotoIdentityResolveHandle: 'com.atproto.identity.resolveHandle', + ComAtprotoIdentitySignPlcOperation: 'com.atproto.identity.signPlcOperation', + ComAtprotoIdentitySubmitPlcOperation: + 'com.atproto.identity.submitPlcOperation', ComAtprotoIdentityUpdateHandle: 'com.atproto.identity.updateHandle', ComAtprotoLabelDefs: 'com.atproto.label.defs', ComAtprotoLabelQueryLabels: 'com.atproto.label.queryLabels', @@ -8594,22 +8934,28 @@ export const ids = { ComAtprotoRepoDeleteRecord: 'com.atproto.repo.deleteRecord', ComAtprotoRepoDescribeRepo: 'com.atproto.repo.describeRepo', ComAtprotoRepoGetRecord: 'com.atproto.repo.getRecord', + ComAtprotoRepoImportRepo: 'com.atproto.repo.importRepo', + ComAtprotoRepoListMissingBlobs: 'com.atproto.repo.listMissingBlobs', ComAtprotoRepoListRecords: 'com.atproto.repo.listRecords', ComAtprotoRepoPutRecord: 'com.atproto.repo.putRecord', ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef', ComAtprotoRepoUploadBlob: 'com.atproto.repo.uploadBlob', + ComAtprotoServerActivateAccount: 'com.atproto.server.activateAccount', + ComAtprotoServerCheckAccountStatus: 'com.atproto.server.checkAccountStatus', ComAtprotoServerConfirmEmail: 'com.atproto.server.confirmEmail', ComAtprotoServerCreateAccount: 'com.atproto.server.createAccount', ComAtprotoServerCreateAppPassword: 'com.atproto.server.createAppPassword', ComAtprotoServerCreateInviteCode: 'com.atproto.server.createInviteCode', ComAtprotoServerCreateInviteCodes: 'com.atproto.server.createInviteCodes', ComAtprotoServerCreateSession: 'com.atproto.server.createSession', + ComAtprotoServerDeactivateAccount: 'com.atproto.server.deactivateAccount', ComAtprotoServerDefs: 'com.atproto.server.defs', ComAtprotoServerDeleteAccount: 'com.atproto.server.deleteAccount', ComAtprotoServerDeleteSession: 'com.atproto.server.deleteSession', ComAtprotoServerDescribeServer: 'com.atproto.server.describeServer', ComAtprotoServerGetAccountInviteCodes: 'com.atproto.server.getAccountInviteCodes', + ComAtprotoServerGetServiceAuth: 'com.atproto.server.getServiceAuth', ComAtprotoServerGetSession: 'com.atproto.server.getSession', ComAtprotoServerListAppPasswords: 'com.atproto.server.listAppPasswords', ComAtprotoServerRefreshSession: 'com.atproto.server.refreshSession', diff --git a/packages/ozone/src/lexicon/types/com/atproto/identity/getRecommendedDidCredentials.ts b/packages/ozone/src/lexicon/types/com/atproto/identity/getRecommendedDidCredentials.ts new file mode 100644 index 00000000000..5fa374de737 --- /dev/null +++ b/packages/ozone/src/lexicon/types/com/atproto/identity/getRecommendedDidCredentials.ts @@ -0,0 +1,47 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export type InputSchema = undefined + +export interface OutputSchema { + /** Recommended rotation keys for PLC dids. Should be undefined (or ignored) for did:webs. */ + rotationKeys?: string[] + alsoKnownAs?: string[] + verificationMethods?: {} + services?: {} + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/ozone/src/lexicon/types/com/atproto/identity/requestPlcOperationSignature.ts b/packages/ozone/src/lexicon/types/com/atproto/identity/requestPlcOperationSignature.ts new file mode 100644 index 00000000000..82672f1d1c7 --- /dev/null +++ b/packages/ozone/src/lexicon/types/com/atproto/identity/requestPlcOperationSignature.ts @@ -0,0 +1,31 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export type InputSchema = undefined +export type HandlerInput = undefined + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/ozone/src/lexicon/types/com/atproto/identity/signPlcOperation.ts b/packages/ozone/src/lexicon/types/com/atproto/identity/signPlcOperation.ts new file mode 100644 index 00000000000..3c908c049f2 --- /dev/null +++ b/packages/ozone/src/lexicon/types/com/atproto/identity/signPlcOperation.ts @@ -0,0 +1,55 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + /** A token received through com.atproto.identity.requestPlcOperationSignature */ + token?: string + rotationKeys?: string[] + alsoKnownAs?: string[] + verificationMethods?: {} + services?: {} + [k: string]: unknown +} + +export interface OutputSchema { + /** A signed DID PLC operation. */ + operation: {} + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/ozone/src/lexicon/types/com/atproto/identity/submitPlcOperation.ts b/packages/ozone/src/lexicon/types/com/atproto/identity/submitPlcOperation.ts new file mode 100644 index 00000000000..5290b55d023 --- /dev/null +++ b/packages/ozone/src/lexicon/types/com/atproto/identity/submitPlcOperation.ts @@ -0,0 +1,38 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + operation: {} + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/ozone/src/lexicon/types/com/atproto/repo/importRepo.ts b/packages/ozone/src/lexicon/types/com/atproto/repo/importRepo.ts new file mode 100644 index 00000000000..921798c0ded --- /dev/null +++ b/packages/ozone/src/lexicon/types/com/atproto/repo/importRepo.ts @@ -0,0 +1,36 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import stream from 'stream' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export type InputSchema = string | Uint8Array + +export interface HandlerInput { + encoding: 'application/vnd.ipld.car' + body: stream.Readable +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/ozone/src/lexicon/types/com/atproto/repo/listMissingBlobs.ts b/packages/ozone/src/lexicon/types/com/atproto/repo/listMissingBlobs.ts new file mode 100644 index 00000000000..40f4d385e47 --- /dev/null +++ b/packages/ozone/src/lexicon/types/com/atproto/repo/listMissingBlobs.ts @@ -0,0 +1,65 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams { + limit: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + blobs: RecordBlob[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput + +export interface RecordBlob { + cid: string + recordUri: string + [k: string]: unknown +} + +export function isRecordBlob(v: unknown): v is RecordBlob { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'com.atproto.repo.listMissingBlobs#recordBlob' + ) +} + +export function validateRecordBlob(v: unknown): ValidationResult { + return lexicons.validate('com.atproto.repo.listMissingBlobs#recordBlob', v) +} diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/activateAccount.ts b/packages/ozone/src/lexicon/types/com/atproto/server/activateAccount.ts new file mode 100644 index 00000000000..82672f1d1c7 --- /dev/null +++ b/packages/ozone/src/lexicon/types/com/atproto/server/activateAccount.ts @@ -0,0 +1,31 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export type InputSchema = undefined +export type HandlerInput = undefined + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/checkAccountStatus.ts b/packages/ozone/src/lexicon/types/com/atproto/server/checkAccountStatus.ts new file mode 100644 index 00000000000..f17182a8dce --- /dev/null +++ b/packages/ozone/src/lexicon/types/com/atproto/server/checkAccountStatus.ts @@ -0,0 +1,51 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export type InputSchema = undefined + +export interface OutputSchema { + activated: boolean + validDid: boolean + repoCommit: string + repoRev: string + repoBlocks: number + indexedRecords: number + privateStateValues: number + expectedBlobs: number + importedBlobs: number + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/deactivateAccount.ts b/packages/ozone/src/lexicon/types/com/atproto/server/deactivateAccount.ts new file mode 100644 index 00000000000..b3793d6b2e0 --- /dev/null +++ b/packages/ozone/src/lexicon/types/com/atproto/server/deactivateAccount.ts @@ -0,0 +1,39 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + /** A recommendation to server as to how long they should hold onto the deactivated account before deleting. */ + deleteAfter?: string + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/describeServer.ts b/packages/ozone/src/lexicon/types/com/atproto/server/describeServer.ts index 0e64c9d9708..c2625347f20 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/server/describeServer.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/server/describeServer.ts @@ -20,6 +20,7 @@ export interface OutputSchema { /** List of domain suffixes that can be used in account handles. */ availableUserDomains: string[] links?: Links + did: string [k: string]: unknown } diff --git a/packages/ozone/src/lexicon/types/com/atproto/server/getServiceAuth.ts b/packages/ozone/src/lexicon/types/com/atproto/server/getServiceAuth.ts new file mode 100644 index 00000000000..73efe2313a9 --- /dev/null +++ b/packages/ozone/src/lexicon/types/com/atproto/server/getServiceAuth.ts @@ -0,0 +1,46 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams { + /** The DID of the service that the token will be used to authenticate with */ + aud: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + token: string + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/api/com/atproto/server/describeServer.ts b/packages/pds/src/api/com/atproto/server/describeServer.ts index 0ad3b2d66eb..28037ba8a55 100644 --- a/packages/pds/src/api/com/atproto/server/describeServer.ts +++ b/packages/pds/src/api/com/atproto/server/describeServer.ts @@ -11,6 +11,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: { + did: ctx.cfg.service.did, availableUserDomains, inviteCodeRequired, links: { privacyPolicy, termsOfService }, diff --git a/packages/pds/src/lexicon/index.ts b/packages/pds/src/lexicon/index.ts index ab207718006..ed08ff55702 100644 --- a/packages/pds/src/lexicon/index.ts +++ b/packages/pds/src/lexicon/index.ts @@ -32,7 +32,11 @@ import * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/up import * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle' import * as ComAtprotoAdminUpdateCommunicationTemplate from './types/com/atproto/admin/updateCommunicationTemplate' import * as ComAtprotoAdminUpdateSubjectStatus from './types/com/atproto/admin/updateSubjectStatus' +import * as ComAtprotoIdentityGetRecommendedDidCredentials from './types/com/atproto/identity/getRecommendedDidCredentials' +import * as ComAtprotoIdentityRequestPlcOperationSignature from './types/com/atproto/identity/requestPlcOperationSignature' import * as ComAtprotoIdentityResolveHandle from './types/com/atproto/identity/resolveHandle' +import * as ComAtprotoIdentitySignPlcOperation from './types/com/atproto/identity/signPlcOperation' +import * as ComAtprotoIdentitySubmitPlcOperation from './types/com/atproto/identity/submitPlcOperation' import * as ComAtprotoIdentityUpdateHandle from './types/com/atproto/identity/updateHandle' import * as ComAtprotoLabelQueryLabels from './types/com/atproto/label/queryLabels' import * as ComAtprotoLabelSubscribeLabels from './types/com/atproto/label/subscribeLabels' @@ -42,19 +46,25 @@ import * as ComAtprotoRepoCreateRecord from './types/com/atproto/repo/createReco import * as ComAtprotoRepoDeleteRecord from './types/com/atproto/repo/deleteRecord' import * as ComAtprotoRepoDescribeRepo from './types/com/atproto/repo/describeRepo' import * as ComAtprotoRepoGetRecord from './types/com/atproto/repo/getRecord' +import * as ComAtprotoRepoImportRepo from './types/com/atproto/repo/importRepo' +import * as ComAtprotoRepoListMissingBlobs from './types/com/atproto/repo/listMissingBlobs' import * as ComAtprotoRepoListRecords from './types/com/atproto/repo/listRecords' import * as ComAtprotoRepoPutRecord from './types/com/atproto/repo/putRecord' import * as ComAtprotoRepoUploadBlob from './types/com/atproto/repo/uploadBlob' +import * as ComAtprotoServerActivateAccount from './types/com/atproto/server/activateAccount' +import * as ComAtprotoServerCheckAccountStatus from './types/com/atproto/server/checkAccountStatus' import * as ComAtprotoServerConfirmEmail from './types/com/atproto/server/confirmEmail' import * as ComAtprotoServerCreateAccount from './types/com/atproto/server/createAccount' import * as ComAtprotoServerCreateAppPassword from './types/com/atproto/server/createAppPassword' import * as ComAtprotoServerCreateInviteCode from './types/com/atproto/server/createInviteCode' import * as ComAtprotoServerCreateInviteCodes from './types/com/atproto/server/createInviteCodes' import * as ComAtprotoServerCreateSession from './types/com/atproto/server/createSession' +import * as ComAtprotoServerDeactivateAccount from './types/com/atproto/server/deactivateAccount' import * as ComAtprotoServerDeleteAccount from './types/com/atproto/server/deleteAccount' import * as ComAtprotoServerDeleteSession from './types/com/atproto/server/deleteSession' import * as ComAtprotoServerDescribeServer from './types/com/atproto/server/describeServer' import * as ComAtprotoServerGetAccountInviteCodes from './types/com/atproto/server/getAccountInviteCodes' +import * as ComAtprotoServerGetServiceAuth from './types/com/atproto/server/getServiceAuth' import * as ComAtprotoServerGetSession from './types/com/atproto/server/getSession' import * as ComAtprotoServerListAppPasswords from './types/com/atproto/server/listAppPasswords' import * as ComAtprotoServerRefreshSession from './types/com/atproto/server/refreshSession' @@ -467,6 +477,32 @@ export class ComAtprotoIdentityNS { this._server = server } + getRecommendedDidCredentials( + cfg: ConfigOf< + AV, + ComAtprotoIdentityGetRecommendedDidCredentials.Handler>, + ComAtprotoIdentityGetRecommendedDidCredentials.HandlerReqCtx< + ExtractAuth + > + >, + ) { + const nsid = 'com.atproto.identity.getRecommendedDidCredentials' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + + requestPlcOperationSignature( + cfg: ConfigOf< + AV, + ComAtprotoIdentityRequestPlcOperationSignature.Handler>, + ComAtprotoIdentityRequestPlcOperationSignature.HandlerReqCtx< + ExtractAuth + > + >, + ) { + const nsid = 'com.atproto.identity.requestPlcOperationSignature' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + resolveHandle( cfg: ConfigOf< AV, @@ -478,6 +514,28 @@ export class ComAtprotoIdentityNS { return this._server.xrpc.method(nsid, cfg) } + signPlcOperation( + cfg: ConfigOf< + AV, + ComAtprotoIdentitySignPlcOperation.Handler>, + ComAtprotoIdentitySignPlcOperation.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.identity.signPlcOperation' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + + submitPlcOperation( + cfg: ConfigOf< + AV, + ComAtprotoIdentitySubmitPlcOperation.Handler>, + ComAtprotoIdentitySubmitPlcOperation.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.identity.submitPlcOperation' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + updateHandle( cfg: ConfigOf< AV, @@ -601,6 +659,28 @@ export class ComAtprotoRepoNS { return this._server.xrpc.method(nsid, cfg) } + importRepo( + cfg: ConfigOf< + AV, + ComAtprotoRepoImportRepo.Handler>, + ComAtprotoRepoImportRepo.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.repo.importRepo' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + + listMissingBlobs( + cfg: ConfigOf< + AV, + ComAtprotoRepoListMissingBlobs.Handler>, + ComAtprotoRepoListMissingBlobs.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.repo.listMissingBlobs' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + listRecords( cfg: ConfigOf< AV, @@ -642,6 +722,28 @@ export class ComAtprotoServerNS { this._server = server } + activateAccount( + cfg: ConfigOf< + AV, + ComAtprotoServerActivateAccount.Handler>, + ComAtprotoServerActivateAccount.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.server.activateAccount' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + + checkAccountStatus( + cfg: ConfigOf< + AV, + ComAtprotoServerCheckAccountStatus.Handler>, + ComAtprotoServerCheckAccountStatus.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.server.checkAccountStatus' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + confirmEmail( cfg: ConfigOf< AV, @@ -708,6 +810,17 @@ export class ComAtprotoServerNS { return this._server.xrpc.method(nsid, cfg) } + deactivateAccount( + cfg: ConfigOf< + AV, + ComAtprotoServerDeactivateAccount.Handler>, + ComAtprotoServerDeactivateAccount.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.server.deactivateAccount' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + deleteAccount( cfg: ConfigOf< AV, @@ -752,6 +865,17 @@ export class ComAtprotoServerNS { return this._server.xrpc.method(nsid, cfg) } + getServiceAuth( + cfg: ConfigOf< + AV, + ComAtprotoServerGetServiceAuth.Handler>, + ComAtprotoServerGetServiceAuth.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.server.getServiceAuth' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + getSession( cfg: ConfigOf< AV, diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index 01a2f683d77..a3491462d50 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -1968,6 +1968,56 @@ export const schemaDict = { }, }, }, + ComAtprotoIdentityGetRecommendedDidCredentials: { + lexicon: 1, + id: 'com.atproto.identity.getRecommendedDidCredentials', + defs: { + main: { + type: 'query', + description: + 'Describe the credentials that should be included in the DID doc of an account that is migrating to this service.', + output: { + encoding: 'application/json', + schema: { + type: 'object', + properties: { + rotationKeys: { + description: + 'Recommended rotation keys for PLC dids. Should be undefined (or ignored) for did:webs.', + type: 'array', + items: { + type: 'string', + }, + }, + alsoKnownAs: { + type: 'array', + items: { + type: 'string', + }, + }, + verificationMethods: { + type: 'unknown', + }, + services: { + type: 'unknown', + }, + }, + }, + }, + }, + }, + }, + ComAtprotoIdentityRequestPlcOperationSignature: { + lexicon: 1, + id: 'com.atproto.identity.requestPlcOperationSignature', + defs: { + main: { + type: 'procedure', + description: + 'Request an email with a code to in order to request a signed PLC operation. Requires Auth.', + }, + }, + }, ComAtprotoIdentityResolveHandle: { lexicon: 1, id: 'com.atproto.identity.resolveHandle', @@ -2002,6 +2052,84 @@ export const schemaDict = { }, }, }, + ComAtprotoIdentitySignPlcOperation: { + lexicon: 1, + id: 'com.atproto.identity.signPlcOperation', + defs: { + main: { + type: 'procedure', + description: + "Signs a PLC operation to update some value(s) in the requesting DID's document.", + input: { + encoding: 'application/json', + schema: { + type: 'object', + properties: { + token: { + description: + 'A token received through com.atproto.identity.requestPlcOperationSignature', + type: 'string', + }, + rotationKeys: { + type: 'array', + items: { + type: 'string', + }, + }, + alsoKnownAs: { + type: 'array', + items: { + type: 'string', + }, + }, + verificationMethods: { + type: 'unknown', + }, + services: { + type: 'unknown', + }, + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['operation'], + properties: { + operation: { + type: 'unknown', + description: 'A signed DID PLC operation.', + }, + }, + }, + }, + }, + }, + }, + ComAtprotoIdentitySubmitPlcOperation: { + lexicon: 1, + id: 'com.atproto.identity.submitPlcOperation', + defs: { + main: { + type: 'procedure', + description: + "Validates a PLC operation to ensure that it doesn't violate a service's constraints or get the identity into a bad state, then submits it to the PLC registry", + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['operation'], + properties: { + operation: { + type: 'unknown', + }, + }, + }, + }, + }, + }, + }, ComAtprotoIdentityUpdateHandle: { lexicon: 1, id: 'com.atproto.identity.updateHandle', @@ -2710,6 +2838,78 @@ export const schemaDict = { }, }, }, + ComAtprotoRepoImportRepo: { + lexicon: 1, + id: 'com.atproto.repo.importRepo', + defs: { + main: { + type: 'procedure', + description: + 'Import a repo in the form of a CAR file. Requires Content-Length HTTP header to be set.', + input: { + encoding: 'application/vnd.ipld.car', + }, + }, + }, + }, + ComAtprotoRepoListMissingBlobs: { + lexicon: 1, + id: 'com.atproto.repo.listMissingBlobs', + defs: { + main: { + type: 'query', + description: + 'Returns a list of missing blobs for the requesting account. Intended to be used in the account migration flow.', + parameters: { + type: 'params', + properties: { + limit: { + type: 'integer', + minimum: 1, + maximum: 1000, + default: 500, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['blobs'], + properties: { + cursor: { + type: 'string', + }, + blobs: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:com.atproto.repo.listMissingBlobs#recordBlob', + }, + }, + }, + }, + }, + }, + recordBlob: { + type: 'object', + required: ['cid', 'recordUri'], + properties: { + cid: { + type: 'string', + format: 'cid', + }, + recordUri: { + type: 'string', + format: 'at-uri', + }, + }, + }, + }, + }, ComAtprotoRepoListRecords: { lexicon: 1, id: 'com.atproto.repo.listRecords', @@ -2925,6 +3125,75 @@ export const schemaDict = { }, }, }, + ComAtprotoServerActivateAccount: { + lexicon: 1, + id: 'com.atproto.server.activateAccount', + defs: { + main: { + type: 'procedure', + description: + "Activates a currently deactivated account. Used to finalize account migration after the account's repo is imported and identity is setup.", + }, + }, + }, + ComAtprotoServerCheckAccountStatus: { + lexicon: 1, + id: 'com.atproto.server.checkAccountStatus', + defs: { + main: { + type: 'query', + description: + 'Returns the status of an account, especially as pertaining to import or recovery. Can be called many times over the course of an account migration. Requires auth and can only be called pertaining to oneself.', + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: [ + 'activated', + 'validDid', + 'repoCommit', + 'repoRev', + 'repoBlocks', + 'indexedRecords', + 'privateStateValues', + 'expectedBlobs', + 'importedBlobs', + ], + properties: { + activated: { + type: 'boolean', + }, + validDid: { + type: 'boolean', + }, + repoCommit: { + type: 'string', + format: 'cid', + }, + repoRev: { + type: 'string', + }, + repoBlocks: { + type: 'integer', + }, + indexedRecords: { + type: 'integer', + }, + privateStateValues: { + type: 'integer', + }, + expectedBlobs: { + type: 'integer', + }, + importedBlobs: { + type: 'integer', + }, + }, + }, + }, + }, + }, + }, ComAtprotoServerConfirmEmail: { lexicon: 1, id: 'com.atproto.server.confirmEmail', @@ -3293,6 +3562,31 @@ export const schemaDict = { }, }, }, + ComAtprotoServerDeactivateAccount: { + lexicon: 1, + id: 'com.atproto.server.deactivateAccount', + defs: { + main: { + type: 'procedure', + description: + 'Deactivates a currently active account. Stops serving of repo, and future writes to repo until reactivated. Used to finalize account migration with the old host after the account has been activated on the new host.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + properties: { + deleteAfter: { + type: 'string', + format: 'datetime', + description: + 'A recommendation to server as to how long they should hold onto the deactivated account before deleting.', + }, + }, + }, + }, + }, + }, + }, ComAtprotoServerDefs: { lexicon: 1, id: 'com.atproto.server.defs', @@ -3413,7 +3707,7 @@ export const schemaDict = { encoding: 'application/json', schema: { type: 'object', - required: ['availableUserDomains'], + required: ['did', 'availableUserDomains'], properties: { inviteCodeRequired: { type: 'boolean', @@ -3438,6 +3732,10 @@ export const schemaDict = { description: 'URLs of service policy documents.', ref: 'lex:com.atproto.server.describeServer#links', }, + did: { + type: 'string', + format: 'did', + }, }, }, }, @@ -3502,6 +3800,41 @@ export const schemaDict = { }, }, }, + ComAtprotoServerGetServiceAuth: { + lexicon: 1, + id: 'com.atproto.server.getServiceAuth', + defs: { + main: { + type: 'query', + description: + 'Get a signed token on behalf of the requesting DID for the requested service.', + parameters: { + type: 'params', + required: ['aud'], + properties: { + aud: { + type: 'string', + format: 'did', + description: + 'The DID of the service that the token will be used to authenticate with', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['token'], + properties: { + token: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, ComAtprotoServerGetSession: { lexicon: 1, id: 'com.atproto.server.getSession', @@ -8582,7 +8915,14 @@ export const ids = { ComAtprotoAdminUpdateCommunicationTemplate: 'com.atproto.admin.updateCommunicationTemplate', ComAtprotoAdminUpdateSubjectStatus: 'com.atproto.admin.updateSubjectStatus', + ComAtprotoIdentityGetRecommendedDidCredentials: + 'com.atproto.identity.getRecommendedDidCredentials', + ComAtprotoIdentityRequestPlcOperationSignature: + 'com.atproto.identity.requestPlcOperationSignature', ComAtprotoIdentityResolveHandle: 'com.atproto.identity.resolveHandle', + ComAtprotoIdentitySignPlcOperation: 'com.atproto.identity.signPlcOperation', + ComAtprotoIdentitySubmitPlcOperation: + 'com.atproto.identity.submitPlcOperation', ComAtprotoIdentityUpdateHandle: 'com.atproto.identity.updateHandle', ComAtprotoLabelDefs: 'com.atproto.label.defs', ComAtprotoLabelQueryLabels: 'com.atproto.label.queryLabels', @@ -8594,22 +8934,28 @@ export const ids = { ComAtprotoRepoDeleteRecord: 'com.atproto.repo.deleteRecord', ComAtprotoRepoDescribeRepo: 'com.atproto.repo.describeRepo', ComAtprotoRepoGetRecord: 'com.atproto.repo.getRecord', + ComAtprotoRepoImportRepo: 'com.atproto.repo.importRepo', + ComAtprotoRepoListMissingBlobs: 'com.atproto.repo.listMissingBlobs', ComAtprotoRepoListRecords: 'com.atproto.repo.listRecords', ComAtprotoRepoPutRecord: 'com.atproto.repo.putRecord', ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef', ComAtprotoRepoUploadBlob: 'com.atproto.repo.uploadBlob', + ComAtprotoServerActivateAccount: 'com.atproto.server.activateAccount', + ComAtprotoServerCheckAccountStatus: 'com.atproto.server.checkAccountStatus', ComAtprotoServerConfirmEmail: 'com.atproto.server.confirmEmail', ComAtprotoServerCreateAccount: 'com.atproto.server.createAccount', ComAtprotoServerCreateAppPassword: 'com.atproto.server.createAppPassword', ComAtprotoServerCreateInviteCode: 'com.atproto.server.createInviteCode', ComAtprotoServerCreateInviteCodes: 'com.atproto.server.createInviteCodes', ComAtprotoServerCreateSession: 'com.atproto.server.createSession', + ComAtprotoServerDeactivateAccount: 'com.atproto.server.deactivateAccount', ComAtprotoServerDefs: 'com.atproto.server.defs', ComAtprotoServerDeleteAccount: 'com.atproto.server.deleteAccount', ComAtprotoServerDeleteSession: 'com.atproto.server.deleteSession', ComAtprotoServerDescribeServer: 'com.atproto.server.describeServer', ComAtprotoServerGetAccountInviteCodes: 'com.atproto.server.getAccountInviteCodes', + ComAtprotoServerGetServiceAuth: 'com.atproto.server.getServiceAuth', ComAtprotoServerGetSession: 'com.atproto.server.getSession', ComAtprotoServerListAppPasswords: 'com.atproto.server.listAppPasswords', ComAtprotoServerRefreshSession: 'com.atproto.server.refreshSession', diff --git a/packages/pds/src/lexicon/types/com/atproto/identity/getRecommendedDidCredentials.ts b/packages/pds/src/lexicon/types/com/atproto/identity/getRecommendedDidCredentials.ts new file mode 100644 index 00000000000..5fa374de737 --- /dev/null +++ b/packages/pds/src/lexicon/types/com/atproto/identity/getRecommendedDidCredentials.ts @@ -0,0 +1,47 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export type InputSchema = undefined + +export interface OutputSchema { + /** Recommended rotation keys for PLC dids. Should be undefined (or ignored) for did:webs. */ + rotationKeys?: string[] + alsoKnownAs?: string[] + verificationMethods?: {} + services?: {} + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/identity/requestPlcOperationSignature.ts b/packages/pds/src/lexicon/types/com/atproto/identity/requestPlcOperationSignature.ts new file mode 100644 index 00000000000..82672f1d1c7 --- /dev/null +++ b/packages/pds/src/lexicon/types/com/atproto/identity/requestPlcOperationSignature.ts @@ -0,0 +1,31 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export type InputSchema = undefined +export type HandlerInput = undefined + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/identity/signPlcOperation.ts b/packages/pds/src/lexicon/types/com/atproto/identity/signPlcOperation.ts new file mode 100644 index 00000000000..3c908c049f2 --- /dev/null +++ b/packages/pds/src/lexicon/types/com/atproto/identity/signPlcOperation.ts @@ -0,0 +1,55 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + /** A token received through com.atproto.identity.requestPlcOperationSignature */ + token?: string + rotationKeys?: string[] + alsoKnownAs?: string[] + verificationMethods?: {} + services?: {} + [k: string]: unknown +} + +export interface OutputSchema { + /** A signed DID PLC operation. */ + operation: {} + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/identity/submitPlcOperation.ts b/packages/pds/src/lexicon/types/com/atproto/identity/submitPlcOperation.ts new file mode 100644 index 00000000000..5290b55d023 --- /dev/null +++ b/packages/pds/src/lexicon/types/com/atproto/identity/submitPlcOperation.ts @@ -0,0 +1,38 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + operation: {} + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/importRepo.ts b/packages/pds/src/lexicon/types/com/atproto/repo/importRepo.ts new file mode 100644 index 00000000000..921798c0ded --- /dev/null +++ b/packages/pds/src/lexicon/types/com/atproto/repo/importRepo.ts @@ -0,0 +1,36 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import stream from 'stream' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export type InputSchema = string | Uint8Array + +export interface HandlerInput { + encoding: 'application/vnd.ipld.car' + body: stream.Readable +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/repo/listMissingBlobs.ts b/packages/pds/src/lexicon/types/com/atproto/repo/listMissingBlobs.ts new file mode 100644 index 00000000000..40f4d385e47 --- /dev/null +++ b/packages/pds/src/lexicon/types/com/atproto/repo/listMissingBlobs.ts @@ -0,0 +1,65 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams { + limit: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + blobs: RecordBlob[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput + +export interface RecordBlob { + cid: string + recordUri: string + [k: string]: unknown +} + +export function isRecordBlob(v: unknown): v is RecordBlob { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'com.atproto.repo.listMissingBlobs#recordBlob' + ) +} + +export function validateRecordBlob(v: unknown): ValidationResult { + return lexicons.validate('com.atproto.repo.listMissingBlobs#recordBlob', v) +} diff --git a/packages/pds/src/lexicon/types/com/atproto/server/activateAccount.ts b/packages/pds/src/lexicon/types/com/atproto/server/activateAccount.ts new file mode 100644 index 00000000000..82672f1d1c7 --- /dev/null +++ b/packages/pds/src/lexicon/types/com/atproto/server/activateAccount.ts @@ -0,0 +1,31 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export type InputSchema = undefined +export type HandlerInput = undefined + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/server/checkAccountStatus.ts b/packages/pds/src/lexicon/types/com/atproto/server/checkAccountStatus.ts new file mode 100644 index 00000000000..f17182a8dce --- /dev/null +++ b/packages/pds/src/lexicon/types/com/atproto/server/checkAccountStatus.ts @@ -0,0 +1,51 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export type InputSchema = undefined + +export interface OutputSchema { + activated: boolean + validDid: boolean + repoCommit: string + repoRev: string + repoBlocks: number + indexedRecords: number + privateStateValues: number + expectedBlobs: number + importedBlobs: number + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/server/deactivateAccount.ts b/packages/pds/src/lexicon/types/com/atproto/server/deactivateAccount.ts new file mode 100644 index 00000000000..b3793d6b2e0 --- /dev/null +++ b/packages/pds/src/lexicon/types/com/atproto/server/deactivateAccount.ts @@ -0,0 +1,39 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + /** A recommendation to server as to how long they should hold onto the deactivated account before deleting. */ + deleteAfter?: string + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/server/describeServer.ts b/packages/pds/src/lexicon/types/com/atproto/server/describeServer.ts index 0e64c9d9708..c2625347f20 100644 --- a/packages/pds/src/lexicon/types/com/atproto/server/describeServer.ts +++ b/packages/pds/src/lexicon/types/com/atproto/server/describeServer.ts @@ -20,6 +20,7 @@ export interface OutputSchema { /** List of domain suffixes that can be used in account handles. */ availableUserDomains: string[] links?: Links + did: string [k: string]: unknown } diff --git a/packages/pds/src/lexicon/types/com/atproto/server/getServiceAuth.ts b/packages/pds/src/lexicon/types/com/atproto/server/getServiceAuth.ts new file mode 100644 index 00000000000..73efe2313a9 --- /dev/null +++ b/packages/pds/src/lexicon/types/com/atproto/server/getServiceAuth.ts @@ -0,0 +1,46 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams { + /** The DID of the service that the token will be used to authenticate with */ + aud: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + token: string + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput From 30b05a7d4bf02273d662098977a1bbb75a977b2b Mon Sep 17 00:00:00 2001 From: Daniel Holmgren Date: Tue, 20 Feb 2024 19:29:49 -0600 Subject: [PATCH 018/103] Account migration (#2179) * draft of account migration lexicons * format * clean up schemas * codegen * second pass on schemas * small fix * move around checkImportStatus * re-codegen * getServiceAuth * getServiceAuth impl * importRepo impl * handle uploadBlob for import * allow bringing your own did on createAccount * working on test flow * fleshing out flow * fix up sendPlcOP * small fixes * activate/deactivate account * full flow working! * schema tweaks * format * update schemas * moar codegen * match impl to new schemas * email flow for signed plc operation * add email flow for plc operations * impl plc op request email * proxy to entryway * tidy activate account * integrating account deactivated state * fix up tests * friendly parse on optional did auth * admin activate/deactivate routes * proxy relevant requests to entryway * remove admin activation routes * do not proxy acitvaition to entryway * cfg for disallowing imports * buff up test * refactor listMissingBlobs a bit * add validDid & activated to accoutn status * emit event on account activation * test creating a post after migrating * account deactivation tests * test name * tests on plc operations * fix recommended did creds * codegen * turn off accepting imports on createAccount * undo prev change * increment version * build branch * pr feedback * handle errs in p-queue * handle blob upload outside of txn * Clean old temp account migration lexicons (#2187) * clean old temp lexicons * rm old test * fix agent session test * fix bsky test * dont build branch --- .../workflows/build-and-push-pds-ghcr.yaml | 1 - lexicons/com/atproto/temp/importRepo.json | 27 -- lexicons/com/atproto/temp/pushBlob.json | 24 -- .../com/atproto/temp/transferAccount.json | 44 -- packages/api/src/client/index.ts | 39 -- packages/api/src/client/lexicons.ts | 133 ------ .../types/com/atproto/temp/importRepo.ts | 33 -- .../client/types/com/atproto/temp/pushBlob.ts | 32 -- .../types/com/atproto/temp/transferAccount.ts | 92 ----- packages/api/tests/agent.test.ts | 1 + packages/bsky/src/lexicon/index.ts | 36 -- packages/bsky/src/lexicon/lexicons.ts | 133 ------ .../types/com/atproto/temp/importRepo.ts | 45 -- .../types/com/atproto/temp/pushBlob.ts | 39 -- .../types/com/atproto/temp/transferAccount.ts | 62 --- .../bsky/tests/auto-moderator/labeler.test.ts | 3 +- packages/common-web/src/did-doc.ts | 5 + packages/ozone/src/lexicon/index.ts | 36 -- packages/ozone/src/lexicon/lexicons.ts | 133 ------ .../types/com/atproto/temp/importRepo.ts | 45 -- .../types/com/atproto/temp/pushBlob.ts | 39 -- .../types/com/atproto/temp/transferAccount.ts | 62 --- packages/pds/package.json | 2 +- .../db/migrations/002-account-deactivation.ts | 17 + .../account-manager/db/migrations/index.ts | 6 +- .../src/account-manager/db/schema/actor.ts | 2 + .../account-manager/db/schema/email-token.ts | 1 + .../src/account-manager/helpers/account.ts | 61 ++- packages/pds/src/account-manager/index.ts | 49 ++- packages/pds/src/actor-store/blob/reader.ts | 55 ++- .../pds/src/actor-store/blob/transactor.ts | 38 +- packages/pds/src/actor-store/index.ts | 29 ++ packages/pds/src/actor-store/record/reader.ts | 10 +- .../src/actor-store/repo/sql-repo-reader.ts | 9 + .../api/com/atproto/admin/getAccountInfo.ts | 5 +- .../src/api/com/atproto/admin/sendEmail.ts | 5 +- .../com/atproto/admin/updateAccountEmail.ts | 5 +- .../com/atproto/admin/updateAccountHandle.ts | 5 +- .../identity/getRecommendedDidCredentials.ts | 45 ++ .../pds/src/api/com/atproto/identity/index.ts | 8 + .../identity/requestPlcOperationSignature.ts | 35 ++ .../api/com/atproto/identity/resolveHandle.ts | 2 +- .../com/atproto/identity/signPlcOperation.ts | 58 +++ .../atproto/identity/submitPlcOperation.ts | 48 +++ .../api/com/atproto/identity/updateHandle.ts | 4 +- .../src/api/com/atproto/repo/applyWrites.ts | 10 +- .../src/api/com/atproto/repo/createRecord.ts | 9 +- .../src/api/com/atproto/repo/deleteRecord.ts | 9 +- .../src/api/com/atproto/repo/importRepo.ts | 135 ++++++ .../pds/src/api/com/atproto/repo/index.ts | 4 + .../api/com/atproto/repo/listMissingBlobs.ts | 23 ++ .../pds/src/api/com/atproto/repo/putRecord.ts | 9 +- .../src/api/com/atproto/repo/uploadBlob.ts | 32 +- .../api/com/atproto/server/activateAccount.ts | 32 ++ .../com/atproto/server/checkAccountStatus.ts | 46 +++ .../api/com/atproto/server/confirmEmail.ts | 4 +- .../api/com/atproto/server/createAccount.ts | 104 +++-- .../api/com/atproto/server/createSession.ts | 10 +- .../com/atproto/server/deactivateAccount.ts | 15 + .../api/com/atproto/server/deleteAccount.ts | 5 +- .../api/com/atproto/server/getServiceAuth.ts | 24 ++ .../pds/src/api/com/atproto/server/index.ts | 9 + .../api/com/atproto/server/refreshSession.ts | 5 +- .../atproto/server/requestAccountDelete.ts | 5 +- .../server/requestEmailConfirmation.ts | 5 +- .../com/atproto/server/requestEmailUpdate.ts | 5 +- .../atproto/server/requestPasswordReset.ts | 5 +- .../src/api/com/atproto/server/updateEmail.ts | 4 +- .../pds/src/api/com/atproto/server/util.ts | 70 ++++ .../pds/src/api/com/atproto/sync/listRepos.ts | 1 + .../src/api/com/atproto/temp/importRepo.ts | 243 ----------- .../pds/src/api/com/atproto/temp/index.ts | 6 - .../pds/src/api/com/atproto/temp/pushBlob.ts | 23 -- .../api/com/atproto/temp/transferAccount.ts | 118 ------ packages/pds/src/auth-verifier.ts | 79 +++- packages/pds/src/config/config.ts | 2 + packages/pds/src/config/env.ts | 2 + packages/pds/src/db/util.ts | 1 + packages/pds/src/lexicon/index.ts | 36 -- packages/pds/src/lexicon/lexicons.ts | 133 ------ .../types/com/atproto/temp/importRepo.ts | 45 -- .../types/com/atproto/temp/pushBlob.ts | 39 -- .../types/com/atproto/temp/transferAccount.ts | 62 --- packages/pds/src/mailer/index.ts | 7 + packages/pds/src/mailer/templates.ts | 1 + .../src/mailer/templates/plc-operation.hbs | 384 ++++++++++++++++++ packages/pds/src/well-known.ts | 2 +- .../pds/tests/account-deactivation.test.ts | 148 +++++++ packages/pds/tests/account-migration.test.ts | 219 ++++++++++ packages/pds/tests/admin-auth.test.ts | 2 +- packages/pds/tests/entryway.test.ts | 12 +- packages/pds/tests/moderation.test.ts | 13 +- packages/pds/tests/plc-operations.test.ts | 226 +++++++++++ packages/pds/tests/transfer-repo.test.ts | 221 ---------- 94 files changed, 2048 insertions(+), 2119 deletions(-) delete mode 100644 lexicons/com/atproto/temp/importRepo.json delete mode 100644 lexicons/com/atproto/temp/pushBlob.json delete mode 100644 lexicons/com/atproto/temp/transferAccount.json delete mode 100644 packages/api/src/client/types/com/atproto/temp/importRepo.ts delete mode 100644 packages/api/src/client/types/com/atproto/temp/pushBlob.ts delete mode 100644 packages/api/src/client/types/com/atproto/temp/transferAccount.ts delete mode 100644 packages/bsky/src/lexicon/types/com/atproto/temp/importRepo.ts delete mode 100644 packages/bsky/src/lexicon/types/com/atproto/temp/pushBlob.ts delete mode 100644 packages/bsky/src/lexicon/types/com/atproto/temp/transferAccount.ts delete mode 100644 packages/ozone/src/lexicon/types/com/atproto/temp/importRepo.ts delete mode 100644 packages/ozone/src/lexicon/types/com/atproto/temp/pushBlob.ts delete mode 100644 packages/ozone/src/lexicon/types/com/atproto/temp/transferAccount.ts create mode 100644 packages/pds/src/account-manager/db/migrations/002-account-deactivation.ts create mode 100644 packages/pds/src/api/com/atproto/identity/getRecommendedDidCredentials.ts create mode 100644 packages/pds/src/api/com/atproto/identity/requestPlcOperationSignature.ts create mode 100644 packages/pds/src/api/com/atproto/identity/signPlcOperation.ts create mode 100644 packages/pds/src/api/com/atproto/identity/submitPlcOperation.ts create mode 100644 packages/pds/src/api/com/atproto/repo/importRepo.ts create mode 100644 packages/pds/src/api/com/atproto/repo/listMissingBlobs.ts create mode 100644 packages/pds/src/api/com/atproto/server/activateAccount.ts create mode 100644 packages/pds/src/api/com/atproto/server/checkAccountStatus.ts create mode 100644 packages/pds/src/api/com/atproto/server/deactivateAccount.ts create mode 100644 packages/pds/src/api/com/atproto/server/getServiceAuth.ts delete mode 100644 packages/pds/src/api/com/atproto/temp/importRepo.ts delete mode 100644 packages/pds/src/api/com/atproto/temp/pushBlob.ts delete mode 100644 packages/pds/src/api/com/atproto/temp/transferAccount.ts delete mode 100644 packages/pds/src/lexicon/types/com/atproto/temp/importRepo.ts delete mode 100644 packages/pds/src/lexicon/types/com/atproto/temp/pushBlob.ts delete mode 100644 packages/pds/src/lexicon/types/com/atproto/temp/transferAccount.ts create mode 100644 packages/pds/src/mailer/templates/plc-operation.hbs create mode 100644 packages/pds/tests/account-deactivation.test.ts create mode 100644 packages/pds/tests/account-migration.test.ts create mode 100644 packages/pds/tests/plc-operations.test.ts delete mode 100644 packages/pds/tests/transfer-repo.test.ts diff --git a/.github/workflows/build-and-push-pds-ghcr.yaml b/.github/workflows/build-and-push-pds-ghcr.yaml index 422894a1bd2..b11230ab531 100644 --- a/.github/workflows/build-and-push-pds-ghcr.yaml +++ b/.github/workflows/build-and-push-pds-ghcr.yaml @@ -3,7 +3,6 @@ on: push: branches: - main - - pds-sanity-check env: REGISTRY: ghcr.io USERNAME: ${{ github.actor }} diff --git a/lexicons/com/atproto/temp/importRepo.json b/lexicons/com/atproto/temp/importRepo.json deleted file mode 100644 index f06daa09d73..00000000000 --- a/lexicons/com/atproto/temp/importRepo.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "lexicon": 1, - "id": "com.atproto.temp.importRepo", - "defs": { - "main": { - "type": "procedure", - "description": "Gets the did's repo, optionally catching up from a specific revision.", - "parameters": { - "type": "params", - "required": ["did"], - "properties": { - "did": { - "type": "string", - "format": "did", - "description": "The DID of the repo." - } - } - }, - "input": { - "encoding": "application/vnd.ipld.car" - }, - "output": { - "encoding": "text/plain" - } - } - } -} diff --git a/lexicons/com/atproto/temp/pushBlob.json b/lexicons/com/atproto/temp/pushBlob.json deleted file mode 100644 index 9babc8f8e43..00000000000 --- a/lexicons/com/atproto/temp/pushBlob.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "lexicon": 1, - "id": "com.atproto.temp.pushBlob", - "defs": { - "main": { - "type": "procedure", - "description": "Gets the did's repo, optionally catching up from a specific revision.", - "parameters": { - "type": "params", - "required": ["did"], - "properties": { - "did": { - "type": "string", - "format": "did", - "description": "The DID of the repo." - } - } - }, - "input": { - "encoding": "*/*" - } - } - } -} diff --git a/lexicons/com/atproto/temp/transferAccount.json b/lexicons/com/atproto/temp/transferAccount.json deleted file mode 100644 index d4687d9b6e1..00000000000 --- a/lexicons/com/atproto/temp/transferAccount.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "lexicon": 1, - "id": "com.atproto.temp.transferAccount", - "defs": { - "main": { - "type": "procedure", - "description": "Transfer an account. NOTE: temporary method, necessarily how account migration will be implemented.", - "input": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["handle", "did", "plcOp"], - "properties": { - "handle": { "type": "string", "format": "handle" }, - "did": { "type": "string", "format": "did" }, - "plcOp": { "type": "unknown" } - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["accessJwt", "refreshJwt", "handle", "did"], - "properties": { - "accessJwt": { "type": "string" }, - "refreshJwt": { "type": "string" }, - "handle": { "type": "string", "format": "handle" }, - "did": { "type": "string", "format": "did" } - } - } - }, - "errors": [ - { "name": "InvalidHandle" }, - { "name": "InvalidPassword" }, - { "name": "InvalidInviteCode" }, - { "name": "HandleNotAvailable" }, - { "name": "UnsupportedDomain" }, - { "name": "UnresolvableDid" }, - { "name": "IncompatibleDidDoc" } - ] - } - } -} diff --git a/packages/api/src/client/index.ts b/packages/api/src/client/index.ts index 42f31866dae..5029db88701 100644 --- a/packages/api/src/client/index.ts +++ b/packages/api/src/client/index.ts @@ -93,10 +93,7 @@ import * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCra import * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos' import * as ComAtprotoTempCheckSignupQueue from './types/com/atproto/temp/checkSignupQueue' import * as ComAtprotoTempFetchLabels from './types/com/atproto/temp/fetchLabels' -import * as ComAtprotoTempImportRepo from './types/com/atproto/temp/importRepo' -import * as ComAtprotoTempPushBlob from './types/com/atproto/temp/pushBlob' import * as ComAtprotoTempRequestPhoneVerification from './types/com/atproto/temp/requestPhoneVerification' -import * as ComAtprotoTempTransferAccount from './types/com/atproto/temp/transferAccount' import * as AppBskyActorDefs from './types/app/bsky/actor/defs' import * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences' import * as AppBskyActorGetProfile from './types/app/bsky/actor/getProfile' @@ -249,10 +246,7 @@ export * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCra export * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos' export * as ComAtprotoTempCheckSignupQueue from './types/com/atproto/temp/checkSignupQueue' export * as ComAtprotoTempFetchLabels from './types/com/atproto/temp/fetchLabels' -export * as ComAtprotoTempImportRepo from './types/com/atproto/temp/importRepo' -export * as ComAtprotoTempPushBlob from './types/com/atproto/temp/pushBlob' export * as ComAtprotoTempRequestPhoneVerification from './types/com/atproto/temp/requestPhoneVerification' -export * as ComAtprotoTempTransferAccount from './types/com/atproto/temp/transferAccount' export * as AppBskyActorDefs from './types/app/bsky/actor/defs' export * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences' export * as AppBskyActorGetProfile from './types/app/bsky/actor/getProfile' @@ -1369,28 +1363,6 @@ export class ComAtprotoTempNS { }) } - importRepo( - data?: ComAtprotoTempImportRepo.InputSchema, - opts?: ComAtprotoTempImportRepo.CallOptions, - ): Promise { - return this._service.xrpc - .call('com.atproto.temp.importRepo', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoTempImportRepo.toKnownErr(e) - }) - } - - pushBlob( - data?: ComAtprotoTempPushBlob.InputSchema, - opts?: ComAtprotoTempPushBlob.CallOptions, - ): Promise { - return this._service.xrpc - .call('com.atproto.temp.pushBlob', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoTempPushBlob.toKnownErr(e) - }) - } - requestPhoneVerification( data?: ComAtprotoTempRequestPhoneVerification.InputSchema, opts?: ComAtprotoTempRequestPhoneVerification.CallOptions, @@ -1401,17 +1373,6 @@ export class ComAtprotoTempNS { throw ComAtprotoTempRequestPhoneVerification.toKnownErr(e) }) } - - transferAccount( - data?: ComAtprotoTempTransferAccount.InputSchema, - opts?: ComAtprotoTempTransferAccount.CallOptions, - ): Promise { - return this._service.xrpc - .call('com.atproto.temp.transferAccount', opts?.qp, data, opts) - .catch((e) => { - throw ComAtprotoTempTransferAccount.toKnownErr(e) - }) - } } export class AppNS { diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index a3491462d50..6164b1706a3 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -4853,59 +4853,6 @@ export const schemaDict = { }, }, }, - ComAtprotoTempImportRepo: { - lexicon: 1, - id: 'com.atproto.temp.importRepo', - defs: { - main: { - type: 'procedure', - description: - "Gets the did's repo, optionally catching up from a specific revision.", - parameters: { - type: 'params', - required: ['did'], - properties: { - did: { - type: 'string', - format: 'did', - description: 'The DID of the repo.', - }, - }, - }, - input: { - encoding: 'application/vnd.ipld.car', - }, - output: { - encoding: 'text/plain', - }, - }, - }, - }, - ComAtprotoTempPushBlob: { - lexicon: 1, - id: 'com.atproto.temp.pushBlob', - defs: { - main: { - type: 'procedure', - description: - "Gets the did's repo, optionally catching up from a specific revision.", - parameters: { - type: 'params', - required: ['did'], - properties: { - did: { - type: 'string', - format: 'did', - description: 'The DID of the repo.', - }, - }, - }, - input: { - encoding: '*/*', - }, - }, - }, - }, ComAtprotoTempRequestPhoneVerification: { lexicon: 1, id: 'com.atproto.temp.requestPhoneVerification', @@ -4929,83 +4876,6 @@ export const schemaDict = { }, }, }, - ComAtprotoTempTransferAccount: { - lexicon: 1, - id: 'com.atproto.temp.transferAccount', - defs: { - main: { - type: 'procedure', - description: - 'Transfer an account. NOTE: temporary method, necessarily how account migration will be implemented.', - input: { - encoding: 'application/json', - schema: { - type: 'object', - required: ['handle', 'did', 'plcOp'], - properties: { - handle: { - type: 'string', - format: 'handle', - }, - did: { - type: 'string', - format: 'did', - }, - plcOp: { - type: 'unknown', - }, - }, - }, - }, - output: { - encoding: 'application/json', - schema: { - type: 'object', - required: ['accessJwt', 'refreshJwt', 'handle', 'did'], - properties: { - accessJwt: { - type: 'string', - }, - refreshJwt: { - type: 'string', - }, - handle: { - type: 'string', - format: 'handle', - }, - did: { - type: 'string', - format: 'did', - }, - }, - }, - }, - errors: [ - { - name: 'InvalidHandle', - }, - { - name: 'InvalidPassword', - }, - { - name: 'InvalidInviteCode', - }, - { - name: 'HandleNotAvailable', - }, - { - name: 'UnsupportedDomain', - }, - { - name: 'UnresolvableDid', - }, - { - name: 'IncompatibleDidDoc', - }, - ], - }, - }, - }, AppBskyActorDefs: { lexicon: 1, id: 'app.bsky.actor.defs', @@ -8984,11 +8854,8 @@ export const ids = { ComAtprotoSyncSubscribeRepos: 'com.atproto.sync.subscribeRepos', ComAtprotoTempCheckSignupQueue: 'com.atproto.temp.checkSignupQueue', ComAtprotoTempFetchLabels: 'com.atproto.temp.fetchLabels', - ComAtprotoTempImportRepo: 'com.atproto.temp.importRepo', - ComAtprotoTempPushBlob: 'com.atproto.temp.pushBlob', ComAtprotoTempRequestPhoneVerification: 'com.atproto.temp.requestPhoneVerification', - ComAtprotoTempTransferAccount: 'com.atproto.temp.transferAccount', AppBskyActorDefs: 'app.bsky.actor.defs', AppBskyActorGetPreferences: 'app.bsky.actor.getPreferences', AppBskyActorGetProfile: 'app.bsky.actor.getProfile', diff --git a/packages/api/src/client/types/com/atproto/temp/importRepo.ts b/packages/api/src/client/types/com/atproto/temp/importRepo.ts deleted file mode 100644 index 6f9f99f2b9d..00000000000 --- a/packages/api/src/client/types/com/atproto/temp/importRepo.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * GENERATED CODE - DO NOT MODIFY - */ -import { Headers, XRPCError } from '@atproto/xrpc' -import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' -import { CID } from 'multiformats/cid' - -export interface QueryParams { - /** The DID of the repo. */ - did: string -} - -export type InputSchema = string | Uint8Array - -export interface CallOptions { - headers?: Headers - qp?: QueryParams - encoding: 'application/vnd.ipld.car' -} - -export interface Response { - success: boolean - headers: Headers - data: Uint8Array -} - -export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } - return e -} diff --git a/packages/api/src/client/types/com/atproto/temp/pushBlob.ts b/packages/api/src/client/types/com/atproto/temp/pushBlob.ts deleted file mode 100644 index 32165bc8014..00000000000 --- a/packages/api/src/client/types/com/atproto/temp/pushBlob.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * GENERATED CODE - DO NOT MODIFY - */ -import { Headers, XRPCError } from '@atproto/xrpc' -import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' -import { CID } from 'multiformats/cid' - -export interface QueryParams { - /** The DID of the repo. */ - did: string -} - -export type InputSchema = string | Uint8Array - -export interface CallOptions { - headers?: Headers - qp?: QueryParams - encoding: string -} - -export interface Response { - success: boolean - headers: Headers -} - -export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - } - return e -} diff --git a/packages/api/src/client/types/com/atproto/temp/transferAccount.ts b/packages/api/src/client/types/com/atproto/temp/transferAccount.ts deleted file mode 100644 index 7ae16c01290..00000000000 --- a/packages/api/src/client/types/com/atproto/temp/transferAccount.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * GENERATED CODE - DO NOT MODIFY - */ -import { Headers, XRPCError } from '@atproto/xrpc' -import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { isObj, hasProp } from '../../../../util' -import { lexicons } from '../../../../lexicons' -import { CID } from 'multiformats/cid' - -export interface QueryParams {} - -export interface InputSchema { - handle: string - did: string - plcOp: {} - [k: string]: unknown -} - -export interface OutputSchema { - accessJwt: string - refreshJwt: string - handle: string - did: string - [k: string]: unknown -} - -export interface CallOptions { - headers?: Headers - qp?: QueryParams - encoding: 'application/json' -} - -export interface Response { - success: boolean - headers: Headers - data: OutputSchema -} - -export class InvalidHandleError extends XRPCError { - constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) - } -} - -export class InvalidPasswordError extends XRPCError { - constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) - } -} - -export class InvalidInviteCodeError extends XRPCError { - constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) - } -} - -export class HandleNotAvailableError extends XRPCError { - constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) - } -} - -export class UnsupportedDomainError extends XRPCError { - constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) - } -} - -export class UnresolvableDidError extends XRPCError { - constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) - } -} - -export class IncompatibleDidDocError extends XRPCError { - constructor(src: XRPCError) { - super(src.status, src.error, src.message, src.headers) - } -} - -export function toKnownErr(e: any) { - if (e instanceof XRPCError) { - if (e.error === 'InvalidHandle') return new InvalidHandleError(e) - if (e.error === 'InvalidPassword') return new InvalidPasswordError(e) - if (e.error === 'InvalidInviteCode') return new InvalidInviteCodeError(e) - if (e.error === 'HandleNotAvailable') return new HandleNotAvailableError(e) - if (e.error === 'UnsupportedDomain') return new UnsupportedDomainError(e) - if (e.error === 'UnresolvableDid') return new UnresolvableDidError(e) - if (e.error === 'IncompatibleDidDoc') return new IncompatibleDidDocError(e) - } - return e -} diff --git a/packages/api/tests/agent.test.ts b/packages/api/tests/agent.test.ts index cff4e3517a8..7bebb8a1bcf 100644 --- a/packages/api/tests/agent.test.ts +++ b/packages/api/tests/agent.test.ts @@ -439,6 +439,7 @@ describe('agent', () => { expect(originalHandlerCallCount).toEqual(1) agent.setPersistSessionHandler(newPersistSession) + agent.session = undefined await agent.createAccount({ handle: 'user8.test', diff --git a/packages/bsky/src/lexicon/index.ts b/packages/bsky/src/lexicon/index.ts index ed08ff55702..6b3b6de582a 100644 --- a/packages/bsky/src/lexicon/index.ts +++ b/packages/bsky/src/lexicon/index.ts @@ -90,10 +90,7 @@ import * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCra import * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos' import * as ComAtprotoTempCheckSignupQueue from './types/com/atproto/temp/checkSignupQueue' import * as ComAtprotoTempFetchLabels from './types/com/atproto/temp/fetchLabels' -import * as ComAtprotoTempImportRepo from './types/com/atproto/temp/importRepo' -import * as ComAtprotoTempPushBlob from './types/com/atproto/temp/pushBlob' import * as ComAtprotoTempRequestPhoneVerification from './types/com/atproto/temp/requestPhoneVerification' -import * as ComAtprotoTempTransferAccount from './types/com/atproto/temp/transferAccount' import * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences' import * as AppBskyActorGetProfile from './types/app/bsky/actor/getProfile' import * as AppBskyActorGetProfiles from './types/app/bsky/actor/getProfiles' @@ -1167,28 +1164,6 @@ export class ComAtprotoTempNS { return this._server.xrpc.method(nsid, cfg) } - importRepo( - cfg: ConfigOf< - AV, - ComAtprotoTempImportRepo.Handler>, - ComAtprotoTempImportRepo.HandlerReqCtx> - >, - ) { - const nsid = 'com.atproto.temp.importRepo' // @ts-ignore - return this._server.xrpc.method(nsid, cfg) - } - - pushBlob( - cfg: ConfigOf< - AV, - ComAtprotoTempPushBlob.Handler>, - ComAtprotoTempPushBlob.HandlerReqCtx> - >, - ) { - const nsid = 'com.atproto.temp.pushBlob' // @ts-ignore - return this._server.xrpc.method(nsid, cfg) - } - requestPhoneVerification( cfg: ConfigOf< AV, @@ -1199,17 +1174,6 @@ export class ComAtprotoTempNS { const nsid = 'com.atproto.temp.requestPhoneVerification' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } - - transferAccount( - cfg: ConfigOf< - AV, - ComAtprotoTempTransferAccount.Handler>, - ComAtprotoTempTransferAccount.HandlerReqCtx> - >, - ) { - const nsid = 'com.atproto.temp.transferAccount' // @ts-ignore - return this._server.xrpc.method(nsid, cfg) - } } export class AppNS { diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index a3491462d50..6164b1706a3 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -4853,59 +4853,6 @@ export const schemaDict = { }, }, }, - ComAtprotoTempImportRepo: { - lexicon: 1, - id: 'com.atproto.temp.importRepo', - defs: { - main: { - type: 'procedure', - description: - "Gets the did's repo, optionally catching up from a specific revision.", - parameters: { - type: 'params', - required: ['did'], - properties: { - did: { - type: 'string', - format: 'did', - description: 'The DID of the repo.', - }, - }, - }, - input: { - encoding: 'application/vnd.ipld.car', - }, - output: { - encoding: 'text/plain', - }, - }, - }, - }, - ComAtprotoTempPushBlob: { - lexicon: 1, - id: 'com.atproto.temp.pushBlob', - defs: { - main: { - type: 'procedure', - description: - "Gets the did's repo, optionally catching up from a specific revision.", - parameters: { - type: 'params', - required: ['did'], - properties: { - did: { - type: 'string', - format: 'did', - description: 'The DID of the repo.', - }, - }, - }, - input: { - encoding: '*/*', - }, - }, - }, - }, ComAtprotoTempRequestPhoneVerification: { lexicon: 1, id: 'com.atproto.temp.requestPhoneVerification', @@ -4929,83 +4876,6 @@ export const schemaDict = { }, }, }, - ComAtprotoTempTransferAccount: { - lexicon: 1, - id: 'com.atproto.temp.transferAccount', - defs: { - main: { - type: 'procedure', - description: - 'Transfer an account. NOTE: temporary method, necessarily how account migration will be implemented.', - input: { - encoding: 'application/json', - schema: { - type: 'object', - required: ['handle', 'did', 'plcOp'], - properties: { - handle: { - type: 'string', - format: 'handle', - }, - did: { - type: 'string', - format: 'did', - }, - plcOp: { - type: 'unknown', - }, - }, - }, - }, - output: { - encoding: 'application/json', - schema: { - type: 'object', - required: ['accessJwt', 'refreshJwt', 'handle', 'did'], - properties: { - accessJwt: { - type: 'string', - }, - refreshJwt: { - type: 'string', - }, - handle: { - type: 'string', - format: 'handle', - }, - did: { - type: 'string', - format: 'did', - }, - }, - }, - }, - errors: [ - { - name: 'InvalidHandle', - }, - { - name: 'InvalidPassword', - }, - { - name: 'InvalidInviteCode', - }, - { - name: 'HandleNotAvailable', - }, - { - name: 'UnsupportedDomain', - }, - { - name: 'UnresolvableDid', - }, - { - name: 'IncompatibleDidDoc', - }, - ], - }, - }, - }, AppBskyActorDefs: { lexicon: 1, id: 'app.bsky.actor.defs', @@ -8984,11 +8854,8 @@ export const ids = { ComAtprotoSyncSubscribeRepos: 'com.atproto.sync.subscribeRepos', ComAtprotoTempCheckSignupQueue: 'com.atproto.temp.checkSignupQueue', ComAtprotoTempFetchLabels: 'com.atproto.temp.fetchLabels', - ComAtprotoTempImportRepo: 'com.atproto.temp.importRepo', - ComAtprotoTempPushBlob: 'com.atproto.temp.pushBlob', ComAtprotoTempRequestPhoneVerification: 'com.atproto.temp.requestPhoneVerification', - ComAtprotoTempTransferAccount: 'com.atproto.temp.transferAccount', AppBskyActorDefs: 'app.bsky.actor.defs', AppBskyActorGetPreferences: 'app.bsky.actor.getPreferences', AppBskyActorGetProfile: 'app.bsky.actor.getProfile', diff --git a/packages/bsky/src/lexicon/types/com/atproto/temp/importRepo.ts b/packages/bsky/src/lexicon/types/com/atproto/temp/importRepo.ts deleted file mode 100644 index 44bce41481c..00000000000 --- a/packages/bsky/src/lexicon/types/com/atproto/temp/importRepo.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * GENERATED CODE - DO NOT MODIFY - */ -import express from 'express' -import stream from 'stream' -import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { lexicons } from '../../../../lexicons' -import { isObj, hasProp } from '../../../../util' -import { CID } from 'multiformats/cid' -import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' - -export interface QueryParams { - /** The DID of the repo. */ - did: string -} - -export type InputSchema = string | Uint8Array - -export interface HandlerInput { - encoding: 'application/vnd.ipld.car' - body: stream.Readable -} - -export interface HandlerSuccess { - encoding: 'text/plain' - body: Uint8Array | stream.Readable - headers?: { [key: string]: string } -} - -export interface HandlerError { - status: number - message?: string -} - -export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough -export type HandlerReqCtx = { - auth: HA - params: QueryParams - input: HandlerInput - req: express.Request - res: express.Response -} -export type Handler = ( - ctx: HandlerReqCtx, -) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/temp/pushBlob.ts b/packages/bsky/src/lexicon/types/com/atproto/temp/pushBlob.ts deleted file mode 100644 index d18a60b598f..00000000000 --- a/packages/bsky/src/lexicon/types/com/atproto/temp/pushBlob.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * GENERATED CODE - DO NOT MODIFY - */ -import express from 'express' -import stream from 'stream' -import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { lexicons } from '../../../../lexicons' -import { isObj, hasProp } from '../../../../util' -import { CID } from 'multiformats/cid' -import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' - -export interface QueryParams { - /** The DID of the repo. */ - did: string -} - -export type InputSchema = string | Uint8Array - -export interface HandlerInput { - encoding: '*/*' - body: stream.Readable -} - -export interface HandlerError { - status: number - message?: string -} - -export type HandlerOutput = HandlerError | void -export type HandlerReqCtx = { - auth: HA - params: QueryParams - input: HandlerInput - req: express.Request - res: express.Response -} -export type Handler = ( - ctx: HandlerReqCtx, -) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/com/atproto/temp/transferAccount.ts b/packages/bsky/src/lexicon/types/com/atproto/temp/transferAccount.ts deleted file mode 100644 index 565150caba2..00000000000 --- a/packages/bsky/src/lexicon/types/com/atproto/temp/transferAccount.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * GENERATED CODE - DO NOT MODIFY - */ -import express from 'express' -import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { lexicons } from '../../../../lexicons' -import { isObj, hasProp } from '../../../../util' -import { CID } from 'multiformats/cid' -import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' - -export interface QueryParams {} - -export interface InputSchema { - handle: string - did: string - plcOp: {} - [k: string]: unknown -} - -export interface OutputSchema { - accessJwt: string - refreshJwt: string - handle: string - did: string - [k: string]: unknown -} - -export interface HandlerInput { - encoding: 'application/json' - body: InputSchema -} - -export interface HandlerSuccess { - encoding: 'application/json' - body: OutputSchema - headers?: { [key: string]: string } -} - -export interface HandlerError { - status: number - message?: string - error?: - | 'InvalidHandle' - | 'InvalidPassword' - | 'InvalidInviteCode' - | 'HandleNotAvailable' - | 'UnsupportedDomain' - | 'UnresolvableDid' - | 'IncompatibleDidDoc' -} - -export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough -export type HandlerReqCtx = { - auth: HA - params: QueryParams - input: HandlerInput - req: express.Request - res: express.Response -} -export type Handler = ( - ctx: HandlerReqCtx, -) => Promise | HandlerOutput diff --git a/packages/bsky/tests/auto-moderator/labeler.test.ts b/packages/bsky/tests/auto-moderator/labeler.test.ts index 5962a3d374c..ceeee8474b2 100644 --- a/packages/bsky/tests/auto-moderator/labeler.test.ts +++ b/packages/bsky/tests/auto-moderator/labeler.test.ts @@ -39,10 +39,11 @@ describe('labeler', () => { alice = sc.dids.alice const storeBlob = (bytes: Uint8Array) => { return pdsCtx.actorStore.transact(alice, async (store) => { - const blobRef = await store.repo.blob.addUntetheredBlob( + const metadata = await store.repo.blob.uploadBlobAndGetMetadata( 'image/jpeg', Readable.from([bytes], { objectMode: false }), ) + const blobRef = await store.repo.blob.trackUntetheredBlob(metadata) const preparedBlobRef = { cid: blobRef.ref, mimeType: 'image/jpeg', diff --git a/packages/common-web/src/did-doc.ts b/packages/common-web/src/did-doc.ts index c2a05b796d3..541e10d0937 100644 --- a/packages/common-web/src/did-doc.ts +++ b/packages/common-web/src/did-doc.ts @@ -44,6 +44,11 @@ export const getSigningKey = ( publicKeyMultibase: found.publicKeyMultibase, } } +export const getSigningDidKey = (doc: DidDocument): string | undefined => { + const parsed = getSigningKey(doc) + if (!parsed) return + return `did:key:${parsed.publicKeyMultibase}` +} export const getPdsEndpoint = (doc: DidDocument): string | undefined => { return getServiceEndpoint(doc, { diff --git a/packages/ozone/src/lexicon/index.ts b/packages/ozone/src/lexicon/index.ts index ed08ff55702..6b3b6de582a 100644 --- a/packages/ozone/src/lexicon/index.ts +++ b/packages/ozone/src/lexicon/index.ts @@ -90,10 +90,7 @@ import * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCra import * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos' import * as ComAtprotoTempCheckSignupQueue from './types/com/atproto/temp/checkSignupQueue' import * as ComAtprotoTempFetchLabels from './types/com/atproto/temp/fetchLabels' -import * as ComAtprotoTempImportRepo from './types/com/atproto/temp/importRepo' -import * as ComAtprotoTempPushBlob from './types/com/atproto/temp/pushBlob' import * as ComAtprotoTempRequestPhoneVerification from './types/com/atproto/temp/requestPhoneVerification' -import * as ComAtprotoTempTransferAccount from './types/com/atproto/temp/transferAccount' import * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences' import * as AppBskyActorGetProfile from './types/app/bsky/actor/getProfile' import * as AppBskyActorGetProfiles from './types/app/bsky/actor/getProfiles' @@ -1167,28 +1164,6 @@ export class ComAtprotoTempNS { return this._server.xrpc.method(nsid, cfg) } - importRepo( - cfg: ConfigOf< - AV, - ComAtprotoTempImportRepo.Handler>, - ComAtprotoTempImportRepo.HandlerReqCtx> - >, - ) { - const nsid = 'com.atproto.temp.importRepo' // @ts-ignore - return this._server.xrpc.method(nsid, cfg) - } - - pushBlob( - cfg: ConfigOf< - AV, - ComAtprotoTempPushBlob.Handler>, - ComAtprotoTempPushBlob.HandlerReqCtx> - >, - ) { - const nsid = 'com.atproto.temp.pushBlob' // @ts-ignore - return this._server.xrpc.method(nsid, cfg) - } - requestPhoneVerification( cfg: ConfigOf< AV, @@ -1199,17 +1174,6 @@ export class ComAtprotoTempNS { const nsid = 'com.atproto.temp.requestPhoneVerification' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } - - transferAccount( - cfg: ConfigOf< - AV, - ComAtprotoTempTransferAccount.Handler>, - ComAtprotoTempTransferAccount.HandlerReqCtx> - >, - ) { - const nsid = 'com.atproto.temp.transferAccount' // @ts-ignore - return this._server.xrpc.method(nsid, cfg) - } } export class AppNS { diff --git a/packages/ozone/src/lexicon/lexicons.ts b/packages/ozone/src/lexicon/lexicons.ts index a3491462d50..6164b1706a3 100644 --- a/packages/ozone/src/lexicon/lexicons.ts +++ b/packages/ozone/src/lexicon/lexicons.ts @@ -4853,59 +4853,6 @@ export const schemaDict = { }, }, }, - ComAtprotoTempImportRepo: { - lexicon: 1, - id: 'com.atproto.temp.importRepo', - defs: { - main: { - type: 'procedure', - description: - "Gets the did's repo, optionally catching up from a specific revision.", - parameters: { - type: 'params', - required: ['did'], - properties: { - did: { - type: 'string', - format: 'did', - description: 'The DID of the repo.', - }, - }, - }, - input: { - encoding: 'application/vnd.ipld.car', - }, - output: { - encoding: 'text/plain', - }, - }, - }, - }, - ComAtprotoTempPushBlob: { - lexicon: 1, - id: 'com.atproto.temp.pushBlob', - defs: { - main: { - type: 'procedure', - description: - "Gets the did's repo, optionally catching up from a specific revision.", - parameters: { - type: 'params', - required: ['did'], - properties: { - did: { - type: 'string', - format: 'did', - description: 'The DID of the repo.', - }, - }, - }, - input: { - encoding: '*/*', - }, - }, - }, - }, ComAtprotoTempRequestPhoneVerification: { lexicon: 1, id: 'com.atproto.temp.requestPhoneVerification', @@ -4929,83 +4876,6 @@ export const schemaDict = { }, }, }, - ComAtprotoTempTransferAccount: { - lexicon: 1, - id: 'com.atproto.temp.transferAccount', - defs: { - main: { - type: 'procedure', - description: - 'Transfer an account. NOTE: temporary method, necessarily how account migration will be implemented.', - input: { - encoding: 'application/json', - schema: { - type: 'object', - required: ['handle', 'did', 'plcOp'], - properties: { - handle: { - type: 'string', - format: 'handle', - }, - did: { - type: 'string', - format: 'did', - }, - plcOp: { - type: 'unknown', - }, - }, - }, - }, - output: { - encoding: 'application/json', - schema: { - type: 'object', - required: ['accessJwt', 'refreshJwt', 'handle', 'did'], - properties: { - accessJwt: { - type: 'string', - }, - refreshJwt: { - type: 'string', - }, - handle: { - type: 'string', - format: 'handle', - }, - did: { - type: 'string', - format: 'did', - }, - }, - }, - }, - errors: [ - { - name: 'InvalidHandle', - }, - { - name: 'InvalidPassword', - }, - { - name: 'InvalidInviteCode', - }, - { - name: 'HandleNotAvailable', - }, - { - name: 'UnsupportedDomain', - }, - { - name: 'UnresolvableDid', - }, - { - name: 'IncompatibleDidDoc', - }, - ], - }, - }, - }, AppBskyActorDefs: { lexicon: 1, id: 'app.bsky.actor.defs', @@ -8984,11 +8854,8 @@ export const ids = { ComAtprotoSyncSubscribeRepos: 'com.atproto.sync.subscribeRepos', ComAtprotoTempCheckSignupQueue: 'com.atproto.temp.checkSignupQueue', ComAtprotoTempFetchLabels: 'com.atproto.temp.fetchLabels', - ComAtprotoTempImportRepo: 'com.atproto.temp.importRepo', - ComAtprotoTempPushBlob: 'com.atproto.temp.pushBlob', ComAtprotoTempRequestPhoneVerification: 'com.atproto.temp.requestPhoneVerification', - ComAtprotoTempTransferAccount: 'com.atproto.temp.transferAccount', AppBskyActorDefs: 'app.bsky.actor.defs', AppBskyActorGetPreferences: 'app.bsky.actor.getPreferences', AppBskyActorGetProfile: 'app.bsky.actor.getProfile', diff --git a/packages/ozone/src/lexicon/types/com/atproto/temp/importRepo.ts b/packages/ozone/src/lexicon/types/com/atproto/temp/importRepo.ts deleted file mode 100644 index 44bce41481c..00000000000 --- a/packages/ozone/src/lexicon/types/com/atproto/temp/importRepo.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * GENERATED CODE - DO NOT MODIFY - */ -import express from 'express' -import stream from 'stream' -import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { lexicons } from '../../../../lexicons' -import { isObj, hasProp } from '../../../../util' -import { CID } from 'multiformats/cid' -import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' - -export interface QueryParams { - /** The DID of the repo. */ - did: string -} - -export type InputSchema = string | Uint8Array - -export interface HandlerInput { - encoding: 'application/vnd.ipld.car' - body: stream.Readable -} - -export interface HandlerSuccess { - encoding: 'text/plain' - body: Uint8Array | stream.Readable - headers?: { [key: string]: string } -} - -export interface HandlerError { - status: number - message?: string -} - -export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough -export type HandlerReqCtx = { - auth: HA - params: QueryParams - input: HandlerInput - req: express.Request - res: express.Response -} -export type Handler = ( - ctx: HandlerReqCtx, -) => Promise | HandlerOutput diff --git a/packages/ozone/src/lexicon/types/com/atproto/temp/pushBlob.ts b/packages/ozone/src/lexicon/types/com/atproto/temp/pushBlob.ts deleted file mode 100644 index d18a60b598f..00000000000 --- a/packages/ozone/src/lexicon/types/com/atproto/temp/pushBlob.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * GENERATED CODE - DO NOT MODIFY - */ -import express from 'express' -import stream from 'stream' -import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { lexicons } from '../../../../lexicons' -import { isObj, hasProp } from '../../../../util' -import { CID } from 'multiformats/cid' -import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' - -export interface QueryParams { - /** The DID of the repo. */ - did: string -} - -export type InputSchema = string | Uint8Array - -export interface HandlerInput { - encoding: '*/*' - body: stream.Readable -} - -export interface HandlerError { - status: number - message?: string -} - -export type HandlerOutput = HandlerError | void -export type HandlerReqCtx = { - auth: HA - params: QueryParams - input: HandlerInput - req: express.Request - res: express.Response -} -export type Handler = ( - ctx: HandlerReqCtx, -) => Promise | HandlerOutput diff --git a/packages/ozone/src/lexicon/types/com/atproto/temp/transferAccount.ts b/packages/ozone/src/lexicon/types/com/atproto/temp/transferAccount.ts deleted file mode 100644 index 565150caba2..00000000000 --- a/packages/ozone/src/lexicon/types/com/atproto/temp/transferAccount.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * GENERATED CODE - DO NOT MODIFY - */ -import express from 'express' -import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { lexicons } from '../../../../lexicons' -import { isObj, hasProp } from '../../../../util' -import { CID } from 'multiformats/cid' -import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' - -export interface QueryParams {} - -export interface InputSchema { - handle: string - did: string - plcOp: {} - [k: string]: unknown -} - -export interface OutputSchema { - accessJwt: string - refreshJwt: string - handle: string - did: string - [k: string]: unknown -} - -export interface HandlerInput { - encoding: 'application/json' - body: InputSchema -} - -export interface HandlerSuccess { - encoding: 'application/json' - body: OutputSchema - headers?: { [key: string]: string } -} - -export interface HandlerError { - status: number - message?: string - error?: - | 'InvalidHandle' - | 'InvalidPassword' - | 'InvalidInviteCode' - | 'HandleNotAvailable' - | 'UnsupportedDomain' - | 'UnresolvableDid' - | 'IncompatibleDidDoc' -} - -export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough -export type HandlerReqCtx = { - auth: HA - params: QueryParams - input: HandlerInput - req: express.Request - res: express.Response -} -export type Handler = ( - ctx: HandlerReqCtx, -) => Promise | HandlerOutput diff --git a/packages/pds/package.json b/packages/pds/package.json index 7476fc650ba..4140b575645 100644 --- a/packages/pds/package.json +++ b/packages/pds/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/pds", - "version": "0.3.19", + "version": "0.4.0-beta", "license": "MIT", "description": "Reference implementation of atproto Personal Data Server (PDS)", "keywords": [ diff --git a/packages/pds/src/account-manager/db/migrations/002-account-deactivation.ts b/packages/pds/src/account-manager/db/migrations/002-account-deactivation.ts new file mode 100644 index 00000000000..fad3c21d230 --- /dev/null +++ b/packages/pds/src/account-manager/db/migrations/002-account-deactivation.ts @@ -0,0 +1,17 @@ +import { Kysely } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .alterTable('actor') + .addColumn('deactivatedAt', 'varchar') + .execute() + await db.schema + .alterTable('actor') + .addColumn('deleteAfter', 'varchar') + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.alterTable('actor').dropColumn('deactivatedAt').execute() + await db.schema.alterTable('actor').dropColumn('deleteAfter').execute() +} diff --git a/packages/pds/src/account-manager/db/migrations/index.ts b/packages/pds/src/account-manager/db/migrations/index.ts index 4b694f0f0f4..154dcd3ea9a 100644 --- a/packages/pds/src/account-manager/db/migrations/index.ts +++ b/packages/pds/src/account-manager/db/migrations/index.ts @@ -1,5 +1,7 @@ -import * as init from './001-init' +import * as mig001 from './001-init' +import * as mig002 from './002-account-deactivation' export default { - '001': init, + '001': mig001, + '002': mig002, } diff --git a/packages/pds/src/account-manager/db/schema/actor.ts b/packages/pds/src/account-manager/db/schema/actor.ts index cfb3fcbe66b..999add6b966 100644 --- a/packages/pds/src/account-manager/db/schema/actor.ts +++ b/packages/pds/src/account-manager/db/schema/actor.ts @@ -5,6 +5,8 @@ export interface Actor { handle: string | null createdAt: string takedownRef: string | null + deactivatedAt: string | null + deleteAfter: string | null } export type ActorEntry = Selectable diff --git a/packages/pds/src/account-manager/db/schema/email-token.ts b/packages/pds/src/account-manager/db/schema/email-token.ts index c544a95ce54..c69a57a0a29 100644 --- a/packages/pds/src/account-manager/db/schema/email-token.ts +++ b/packages/pds/src/account-manager/db/schema/email-token.ts @@ -3,6 +3,7 @@ export type EmailTokenPurpose = | 'update_email' | 'reset_password' | 'delete_account' + | 'plc_operation' export interface EmailToken { purpose: EmailTokenPurpose diff --git a/packages/pds/src/account-manager/helpers/account.ts b/packages/pds/src/account-manager/helpers/account.ts index f0e87c6d0ed..344c06a3778 100644 --- a/packages/pds/src/account-manager/helpers/account.ts +++ b/packages/pds/src/account-manager/helpers/account.ts @@ -1,6 +1,7 @@ import { isErrUniqueViolation, notSoftDeletedClause } from '../../db' import { AccountDb, ActorEntry } from '../db' import { StatusAttr } from '../../lexicon/types/com/atproto/admin/defs' +import { DAY } from '@atproto/common' export class UserAlreadyExistsError extends Error {} @@ -10,19 +11,28 @@ export type ActorAccount = ActorEntry & { invitesDisabled: 0 | 1 | null } -const selectAccountQB = (db: AccountDb, includeSoftDeleted: boolean) => { +export type AvailabilityFlags = { + includeTakenDown?: boolean + includeDeactivated?: boolean +} + +const selectAccountQB = (db: AccountDb, flags?: AvailabilityFlags) => { + const { includeTakenDown = false, includeDeactivated = false } = flags ?? {} const { ref } = db.db.dynamic return db.db .selectFrom('actor') .leftJoin('account', 'actor.did', 'account.did') - .if(!includeSoftDeleted, (qb) => - qb.where(notSoftDeletedClause(ref('actor'))), + .if(!includeTakenDown, (qb) => qb.where(notSoftDeletedClause(ref('actor')))) + .if(!includeDeactivated, (qb) => + qb.where('actor.deactivatedAt', 'is', null), ) .select([ 'actor.did', 'actor.handle', 'actor.createdAt', 'actor.takedownRef', + 'actor.deactivatedAt', + 'actor.deleteAfter', 'account.email', 'account.emailConfirmedAt', 'account.invitesDisabled', @@ -32,9 +42,9 @@ const selectAccountQB = (db: AccountDb, includeSoftDeleted: boolean) => { export const getAccount = async ( db: AccountDb, handleOrDid: string, - includeSoftDeleted = false, + flags?: AvailabilityFlags, ): Promise => { - const found = await selectAccountQB(db, includeSoftDeleted) + const found = await selectAccountQB(db, flags) .where((qb) => { if (handleOrDid.startsWith('did:')) { return qb.where('actor.did', '=', handleOrDid) @@ -49,9 +59,9 @@ export const getAccount = async ( export const getAccountByEmail = async ( db: AccountDb, email: string, - includeSoftDeleted = false, + flags?: AvailabilityFlags, ): Promise => { - const found = await selectAccountQB(db, includeSoftDeleted) + const found = await selectAccountQB(db, flags) .where('email', '=', email.toLowerCase()) .executeTakeFirst() return found || null @@ -62,16 +72,21 @@ export const registerActor = async ( opts: { did: string handle: string + deactivated?: boolean }, ) => { - const { did, handle } = opts + const { did, handle, deactivated } = opts + const now = Date.now() + const createdAt = new Date(now).toISOString() const [registered] = await db.executeWithRetry( db.db .insertInto('actor') .values({ did, handle, - createdAt: new Date().toISOString(), + createdAt, + deactivatedAt: deactivated ? createdAt : null, + deleteAfter: deactivated ? new Date(now + 3 * DAY).toISOString() : null, }) .onConflict((oc) => oc.doNothing()) .returning('did'), @@ -208,3 +223,31 @@ export const updateAccountTakedownStatus = async ( db.db.updateTable('actor').set({ takedownRef }).where('did', '=', did), ) } + +export const deactivateAccount = async ( + db: AccountDb, + did: string, + deleteAfter: string | null, +) => { + await db.executeWithRetry( + db.db + .updateTable('actor') + .set({ + deactivatedAt: new Date().toISOString(), + deleteAfter, + }) + .where('did', '=', did), + ) +} + +export const activateAccount = async (db: AccountDb, did: string) => { + await db.executeWithRetry( + db.db + .updateTable('actor') + .set({ + deactivatedAt: null, + deleteAfter: null, + }) + .where('did', '=', did), + ) +} diff --git a/packages/pds/src/account-manager/index.ts b/packages/pds/src/account-manager/index.ts index 469d942aea9..d071550ee69 100644 --- a/packages/pds/src/account-manager/index.ts +++ b/packages/pds/src/account-manager/index.ts @@ -39,16 +39,16 @@ export class AccountManager { async getAccount( handleOrDid: string, - includeSoftDeleted = false, + flags?: account.AvailabilityFlags, ): Promise { - return account.getAccount(this.db, handleOrDid, includeSoftDeleted) + return account.getAccount(this.db, handleOrDid, flags) } async getAccountByEmail( email: string, - includeSoftDeleted = false, + flags?: account.AvailabilityFlags, ): Promise { - return account.getAccountByEmail(this.db, email, includeSoftDeleted) + return account.getAccountByEmail(this.db, email, flags) } // Repo exists and is not taken-down @@ -57,11 +57,17 @@ export class AccountManager { return !!got } + async isAccountActivated(did: string): Promise { + const account = await this.getAccount(did, { includeDeactivated: true }) + if (!account) return false + return !account.deactivatedAt + } + async getDidForActor( handleOrDid: string, - includeSoftDeleted = false, + flags?: account.AvailabilityFlags, ): Promise { - const got = await this.getAccount(handleOrDid, includeSoftDeleted) + const got = await this.getAccount(handleOrDid, flags) return got?.did ?? null } @@ -73,8 +79,18 @@ export class AccountManager { repoCid: CID repoRev: string inviteCode?: string + deactivated?: boolean }) { - const { did, handle, email, password, repoCid, repoRev, inviteCode } = opts + const { + did, + handle, + email, + password, + repoCid, + repoRev, + inviteCode, + deactivated, + } = opts const passwordScrypt = password ? await scrypt.genSaltAndHash(password) : undefined @@ -92,7 +108,7 @@ export class AccountManager { await invite.ensureInviteIsAvailable(dbTxn, inviteCode) } await Promise.all([ - account.registerActor(dbTxn, { did, handle }), + account.registerActor(dbTxn, { did, handle, deactivated }), email && passwordScrypt ? account.registerAccount(dbTxn, { did, email, passwordScrypt }) : Promise.resolve(), @@ -135,6 +151,14 @@ export class AccountManager { return repo.updateRoot(this.db, did, cid, rev) } + async deactivateAccount(did: string, deleteAfter: string | null) { + return account.deactivateAccount(this.db, did, deleteAfter) + } + + async activateAccount(did: string) { + return account.activateAccount(this.db, did) + } + // Auth // ---------- @@ -309,6 +333,15 @@ export class AccountManager { return emailToken.assertValidToken(this.db, did, purpose, token) } + async assertValidEmailTokenAndCleanup( + did: string, + purpose: EmailTokenPurpose, + token: string, + ) { + await emailToken.assertValidToken(this.db, did, purpose, token) + await emailToken.deleteEmailToken(this.db, did, purpose) + } + async confirmEmail(opts: { did: string; token: string }) { const { did, token } = opts await emailToken.assertValidToken(this.db, did, 'confirm_email', token) diff --git a/packages/pds/src/actor-store/blob/reader.ts b/packages/pds/src/actor-store/blob/reader.ts index bb38ed92e69..299358b1119 100644 --- a/packages/pds/src/actor-store/blob/reader.ts +++ b/packages/pds/src/actor-store/blob/reader.ts @@ -3,7 +3,7 @@ import { CID } from 'multiformats/cid' import { BlobNotFoundError, BlobStore } from '@atproto/repo' import { InvalidRequestError } from '@atproto/xrpc-server' import { ActorDb } from '../db' -import { notSoftDeletedClause } from '../../db/util' +import { countAll, countDistinct, notSoftDeletedClause } from '../../db/util' import { StatusAttr } from '../../lexicon/types/com/atproto/admin/defs' export class BlobReader { @@ -73,4 +73,57 @@ export class BlobReader { ? { applied: true, ref: res.takedownRef } : { applied: false } } + + async getRecordsForBlob(cid: CID): Promise { + const res = await this.db.db + .selectFrom('record_blob') + .where('blobCid', '=', cid.toString()) + .selectAll() + .execute() + return res.map((row) => row.recordUri) + } + + async blobCount(): Promise { + const res = await this.db.db + .selectFrom('blob') + .select(countAll.as('count')) + .executeTakeFirst() + return res?.count ?? 0 + } + + async recordBlobCount(): Promise { + const { ref } = this.db.db.dynamic + const res = await this.db.db + .selectFrom('record_blob') + .select(countDistinct(ref('blobCid')).as('count')) + .executeTakeFirst() + return res?.count ?? 0 + } + + async listMissingBlobs(opts: { + cursor?: string + limit: number + }): Promise<{ cid: string; recordUri: string }[]> { + const { cursor, limit } = opts + let builder = this.db.db + .selectFrom('record_blob') + .whereNotExists((qb) => + qb + .selectFrom('blob') + .selectAll() + .whereRef('blob.cid', '=', 'record_blob.blobCid'), + ) + .selectAll() + .orderBy('blobCid', 'asc') + .groupBy('blobCid') + .limit(limit) + if (cursor) { + builder = builder.where('blobCid', '>', cursor) + } + const res = await builder.execute() + return res.map((row) => ({ + cid: row.blobCid, + recordUri: row.recordUri, + })) + } } diff --git a/packages/pds/src/actor-store/blob/transactor.ts b/packages/pds/src/actor-store/blob/transactor.ts index 013235639a0..fa937e6bec6 100644 --- a/packages/pds/src/actor-store/blob/transactor.ts +++ b/packages/pds/src/actor-store/blob/transactor.ts @@ -20,6 +20,15 @@ import { BackgroundQueue } from '../../background' import { BlobReader } from './reader' import { StatusAttr } from '../../lexicon/types/com/atproto/admin/defs' +export type BlobMetadata = { + tempKey: string + size: number + cid: CID + mimeType: string + width: number | null + height: number | null +} + export class BlobTransactor extends BlobReader { constructor( public db: ActorDb, @@ -29,10 +38,10 @@ export class BlobTransactor extends BlobReader { super(db, blobstore) } - async addUntetheredBlob( + async uploadBlobAndGetMetadata( userSuggestedMime: string, blobStream: stream.Readable, - ): Promise { + ): Promise { const [tempKey, size, sha256, imgInfo, sniffedMime] = await Promise.all([ this.blobstore.putTemp(cloneStream(blobStream)), streamSize(cloneStream(blobStream)), @@ -44,6 +53,27 @@ export class BlobTransactor extends BlobReader { const cid = sha256RawToCid(sha256) const mimeType = sniffedMime || userSuggestedMime + return { + tempKey, + size, + cid, + mimeType, + width: imgInfo?.width ?? null, + height: imgInfo?.height ?? null, + } + } + + async trackUntetheredBlob(metadata: BlobMetadata) { + const { tempKey, size, cid, mimeType, width, height } = metadata + const found = await this.db.db + .selectFrom('blob') + .selectAll() + .where('cid', '=', cid.toString()) + .executeTakeFirst() + if (found?.takedownRef) { + throw new InvalidRequestError('Blob has been takendown, cannot re-upload') + } + await this.db.db .insertInto('blob') .values({ @@ -51,8 +81,8 @@ export class BlobTransactor extends BlobReader { mimeType, size, tempKey, - width: imgInfo?.width || null, - height: imgInfo?.height || null, + width, + height, createdAt: new Date().toISOString(), }) .onConflict((oc) => diff --git a/packages/pds/src/actor-store/index.ts b/packages/pds/src/actor-store/index.ts index 1ac7ea52bb3..698956b65f5 100644 --- a/packages/pds/src/actor-store/index.ts +++ b/packages/pds/src/actor-store/index.ts @@ -107,6 +107,30 @@ export class ActorStore { } } + async writeNoTransaction(did: string, fn: ActorStoreWriterFn) { + const keypair = await this.keypair(did) + const db = await this.openDb(did) + try { + const writer = createActorTransactor(did, db, keypair, this.resources) + return await fn({ + ...writer, + transact: async (fn: ActorStoreTransactFn): Promise => { + return db.transaction((dbTxn) => { + const transactor = createActorTransactor( + did, + dbTxn, + keypair, + this.resources, + ) + return fn(transactor) + }) + }, + }) + } finally { + db.close() + } + } + async create(did: string, keypair: ExportableKeypair) { const { directory, dbLocation, keyLocation } = await this.getLocation(did) // ensure subdir exists @@ -244,6 +268,7 @@ const createActorReader = ( export type ActorStoreReadFn = (fn: ActorStoreReader) => Promise export type ActorStoreTransactFn = (fn: ActorStoreTransactor) => Promise +export type ActorStoreWriterFn = (fn: ActorStoreWriter) => Promise export type ActorStoreReader = { did: string @@ -262,6 +287,10 @@ export type ActorStoreTransactor = { pref: PreferenceTransactor } +export type ActorStoreWriter = ActorStoreTransactor & { + transact: (fn: ActorStoreTransactFn) => Promise +} + function assertSafePathPart(part: string) { const normalized = path.normalize(part) assert( diff --git a/packages/pds/src/actor-store/record/reader.ts b/packages/pds/src/actor-store/record/reader.ts index ed8d231f3cf..7c7b3383439 100644 --- a/packages/pds/src/actor-store/record/reader.ts +++ b/packages/pds/src/actor-store/record/reader.ts @@ -2,7 +2,7 @@ import * as syntax from '@atproto/syntax' import { AtUri, ensureValidAtUri } from '@atproto/syntax' import { cborToLexRecord } from '@atproto/repo' import { CID } from 'multiformats/cid' -import { notSoftDeletedClause } from '../../db/util' +import { countAll, notSoftDeletedClause } from '../../db/util' import { ids } from '../../lexicon/lexicons' import { ActorDb, Backlink } from '../db' import { StatusAttr } from '../../lexicon/types/com/atproto/admin/defs' @@ -11,6 +11,14 @@ import { RepoRecord } from '@atproto/lexicon' export class RecordReader { constructor(public db: ActorDb) {} + async recordCount(): Promise { + const res = await this.db.db + .selectFrom('record') + .select(countAll.as('count')) + .executeTakeFirst() + return res?.count ?? 0 + } + async listCollections(): Promise { const collections = await this.db.db .selectFrom('record') diff --git a/packages/pds/src/actor-store/repo/sql-repo-reader.ts b/packages/pds/src/actor-store/repo/sql-repo-reader.ts index 90b61b08e68..30f994031c4 100644 --- a/packages/pds/src/actor-store/repo/sql-repo-reader.ts +++ b/packages/pds/src/actor-store/repo/sql-repo-reader.ts @@ -8,6 +8,7 @@ import { chunkArray } from '@atproto/common' import { CID } from 'multiformats/cid' import { ActorDb } from '../db' import { sql } from 'kysely' +import { countAll } from '../../db' export class SqlRepoReader extends ReadableBlockstore { cache: BlockMap = new BlockMap() @@ -136,6 +137,14 @@ export class SqlRepoReader extends ReadableBlockstore { return builder.execute() } + async countBlocks(): Promise { + const res = await this.db.db + .selectFrom('repo_block') + .select(countAll.as('count')) + .executeTakeFirst() + return res?.count ?? 0 + } + async destroy(): Promise { throw new Error('Destruction of SQL repo storage not allowed at runtime') } diff --git a/packages/pds/src/api/com/atproto/admin/getAccountInfo.ts b/packages/pds/src/api/com/atproto/admin/getAccountInfo.ts index 7b94a8c57d8..e258f9714b2 100644 --- a/packages/pds/src/api/com/atproto/admin/getAccountInfo.ts +++ b/packages/pds/src/api/com/atproto/admin/getAccountInfo.ts @@ -8,7 +8,10 @@ export default function (server: Server, ctx: AppContext) { auth: ctx.authVerifier.roleOrAdminService, handler: async ({ params }) => { const [account, invites, invitedBy] = await Promise.all([ - ctx.accountManager.getAccount(params.did, true), + ctx.accountManager.getAccount(params.did, { + includeDeactivated: true, + includeTakenDown: true, + }), ctx.accountManager.getAccountInvitesCodes(params.did), ctx.accountManager.getInvitedByForAccounts([params.did]), ]) diff --git a/packages/pds/src/api/com/atproto/admin/sendEmail.ts b/packages/pds/src/api/com/atproto/admin/sendEmail.ts index 169eaf4e6e2..b61e9e0d158 100644 --- a/packages/pds/src/api/com/atproto/admin/sendEmail.ts +++ b/packages/pds/src/api/com/atproto/admin/sendEmail.ts @@ -18,7 +18,10 @@ export default function (server: Server, ctx: AppContext) { subject = 'Message from Bluesky moderator', comment, } = input.body - const account = await ctx.accountManager.getAccount(recipientDid) + const account = await ctx.accountManager.getAccount(recipientDid, { + includeDeactivated: true, + includeTakenDown: true, + }) if (!account) { throw new InvalidRequestError('Recipient not found') } diff --git a/packages/pds/src/api/com/atproto/admin/updateAccountEmail.ts b/packages/pds/src/api/com/atproto/admin/updateAccountEmail.ts index 6e42905b83d..c2f266d716d 100644 --- a/packages/pds/src/api/com/atproto/admin/updateAccountEmail.ts +++ b/packages/pds/src/api/com/atproto/admin/updateAccountEmail.ts @@ -10,7 +10,10 @@ export default function (server: Server, ctx: AppContext) { if (!auth.credentials.admin) { throw new AuthRequiredError('Insufficient privileges') } - const account = await ctx.accountManager.getAccount(input.body.account) + const account = await ctx.accountManager.getAccount(input.body.account, { + includeDeactivated: true, + includeTakenDown: true, + }) if (!account) { throw new InvalidRequestError( `Account does not exist: ${input.body.account}`, diff --git a/packages/pds/src/api/com/atproto/admin/updateAccountHandle.ts b/packages/pds/src/api/com/atproto/admin/updateAccountHandle.ts index b13d1ee54aa..627f1aaebc9 100644 --- a/packages/pds/src/api/com/atproto/admin/updateAccountHandle.ts +++ b/packages/pds/src/api/com/atproto/admin/updateAccountHandle.ts @@ -21,7 +21,10 @@ export default function (server: Server, ctx: AppContext) { }) // Pessimistic check to handle spam: also enforced by updateHandle() and the db. - const account = await ctx.accountManager.getAccount(handle) + const account = await ctx.accountManager.getAccount(handle, { + includeDeactivated: true, + includeTakenDown: true, + }) if (account) { if (account.did !== did) { diff --git a/packages/pds/src/api/com/atproto/identity/getRecommendedDidCredentials.ts b/packages/pds/src/api/com/atproto/identity/getRecommendedDidCredentials.ts new file mode 100644 index 00000000000..cd163f16566 --- /dev/null +++ b/packages/pds/src/api/com/atproto/identity/getRecommendedDidCredentials.ts @@ -0,0 +1,45 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' + +export default function (server: Server, ctx: AppContext) { + server.com.atproto.identity.getRecommendedDidCredentials({ + auth: ctx.authVerifier.access, + handler: async ({ auth }) => { + const requester = auth.credentials.did + const signingKey = await ctx.actorStore.keypair(requester) + const verificationMethods = { + atproto: signingKey.did(), + } + const account = await ctx.accountManager.getAccount(requester, { + includeDeactivated: true, + }) + const alsoKnownAs = account?.handle + ? [`at://${account.handle}`] + : undefined + + const plcRotationKey = + ctx.cfg.entryway?.plcRotationKey ?? ctx.plcRotationKey.did() + const rotationKeys = [plcRotationKey] + if (ctx.cfg.identity.recoveryDidKey) { + rotationKeys.unshift(ctx.cfg.identity.recoveryDidKey) + } + + const services = { + atproto_pds: { + type: 'AtprotoPersonalDataServer', + endpoint: ctx.cfg.service.publicUrl, + }, + } + + return { + encoding: 'application/json', + body: { + alsoKnownAs, + verificationMethods, + rotationKeys, + services, + }, + } + }, + }) +} diff --git a/packages/pds/src/api/com/atproto/identity/index.ts b/packages/pds/src/api/com/atproto/identity/index.ts index 724b82de197..0a1d72a919b 100644 --- a/packages/pds/src/api/com/atproto/identity/index.ts +++ b/packages/pds/src/api/com/atproto/identity/index.ts @@ -2,8 +2,16 @@ import AppContext from '../../../../context' import { Server } from '../../../../lexicon' import resolveHandle from './resolveHandle' import updateHandle from './updateHandle' +import getRecommendedDidCredentials from './getRecommendedDidCredentials' +import requestPlcOperationSignature from './requestPlcOperationSignature' +import signPlcOperation from './signPlcOperation' +import submitPlcOperation from './submitPlcOperation' export default function (server: Server, ctx: AppContext) { resolveHandle(server, ctx) updateHandle(server, ctx) + getRecommendedDidCredentials(server, ctx) + requestPlcOperationSignature(server, ctx) + signPlcOperation(server, ctx) + submitPlcOperation(server, ctx) } diff --git a/packages/pds/src/api/com/atproto/identity/requestPlcOperationSignature.ts b/packages/pds/src/api/com/atproto/identity/requestPlcOperationSignature.ts new file mode 100644 index 00000000000..2c49a4b983b --- /dev/null +++ b/packages/pds/src/api/com/atproto/identity/requestPlcOperationSignature.ts @@ -0,0 +1,35 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { InvalidRequestError } from '@atproto/xrpc-server' +import { authPassthru } from '../../../proxy' + +export default function (server: Server, ctx: AppContext) { + server.com.atproto.identity.requestPlcOperationSignature({ + auth: ctx.authVerifier.accessNotAppPassword, + handler: async ({ auth, req }) => { + if (ctx.entrywayAgent) { + await ctx.entrywayAgent.com.atproto.identity.requestPlcOperationSignature( + undefined, + authPassthru(req), + ) + return + } + + const did = auth.credentials.did + const account = await ctx.accountManager.getAccount(did, { + includeDeactivated: true, + includeTakenDown: true, + }) + if (!account) { + throw new InvalidRequestError('account not found') + } else if (!account.email) { + throw new InvalidRequestError('account does not have an email address') + } + const token = await ctx.accountManager.createEmailToken( + did, + 'plc_operation', + ) + await ctx.mailer.sendPlcOperation({ token }, { to: account.email }) + }, + }) +} diff --git a/packages/pds/src/api/com/atproto/identity/resolveHandle.ts b/packages/pds/src/api/com/atproto/identity/resolveHandle.ts index 472d7ccb234..67e0af41520 100644 --- a/packages/pds/src/api/com/atproto/identity/resolveHandle.ts +++ b/packages/pds/src/api/com/atproto/identity/resolveHandle.ts @@ -18,7 +18,7 @@ export default function (server: Server, ctx: AppContext) { } let did: string | undefined - const user = await ctx.accountManager.getAccount(handle, true) + const user = await ctx.accountManager.getAccount(handle) if (user) { did = user.did diff --git a/packages/pds/src/api/com/atproto/identity/signPlcOperation.ts b/packages/pds/src/api/com/atproto/identity/signPlcOperation.ts new file mode 100644 index 00000000000..95381aa8588 --- /dev/null +++ b/packages/pds/src/api/com/atproto/identity/signPlcOperation.ts @@ -0,0 +1,58 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import * as plc from '@did-plc/lib' +import { check } from '@atproto/common' +import { InvalidRequestError } from '@atproto/xrpc-server' +import { authPassthru, resultPassthru } from '../../../proxy' + +export default function (server: Server, ctx: AppContext) { + server.com.atproto.identity.signPlcOperation({ + auth: ctx.authVerifier.accessNotAppPassword, + handler: async ({ auth, input, req }) => { + if (ctx.entrywayAgent) { + return resultPassthru( + await ctx.entrywayAgent.com.atproto.identity.signPlcOperation( + input.body, + authPassthru(req, true), + ), + ) + } + + const did = auth.credentials.did + const { token } = input.body + if (!token) { + throw new InvalidRequestError( + 'email confirmation token required to sign PLC operations', + ) + } + await ctx.accountManager.assertValidEmailTokenAndCleanup( + did, + 'plc_operation', + token, + ) + + const lastOp = await ctx.plcClient.getLastOp(did) + if (check.is(lastOp, plc.def.tombstone)) { + throw new InvalidRequestError('Did is tombstoned') + } + const operation = await plc.createUpdateOp( + lastOp, + ctx.plcRotationKey, + (lastOp) => ({ + ...lastOp, + rotationKeys: input.body.rotationKeys ?? lastOp.rotationKeys, + alsoKnownAs: input.body.alsoKnownAs ?? lastOp.alsoKnownAs, + verificationMethods: + input.body.verificationMethods ?? lastOp.verificationMethods, + services: input.body.services ?? lastOp.services, + }), + ) + return { + encoding: 'application/json', + body: { + operation, + }, + } + }, + }) +} diff --git a/packages/pds/src/api/com/atproto/identity/submitPlcOperation.ts b/packages/pds/src/api/com/atproto/identity/submitPlcOperation.ts new file mode 100644 index 00000000000..66db47fe482 --- /dev/null +++ b/packages/pds/src/api/com/atproto/identity/submitPlcOperation.ts @@ -0,0 +1,48 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import * as plc from '@did-plc/lib' +import { check } from '@atproto/common' +import { InvalidRequestError } from '@atproto/xrpc-server' + +export default function (server: Server, ctx: AppContext) { + server.com.atproto.identity.submitPlcOperation({ + auth: ctx.authVerifier.access, + handler: async ({ auth, input }) => { + const requester = auth.credentials.did + const op = input.body.operation + + if (!check.is(op, plc.def.operation)) { + throw new InvalidRequestError('Invalid operation') + } + + if (!op.rotationKeys.includes(ctx.plcRotationKey.did())) { + throw new InvalidRequestError( + "Rotation keys do not include server's rotation key", + ) + } + if (op.services['atproto_pds']?.type !== 'AtprotoPersonalDataServer') { + throw new InvalidRequestError('Incorrect type on atproto_pds service') + } + if (op.services['atproto_pds']?.endpoint !== ctx.cfg.service.publicUrl) { + throw new InvalidRequestError( + 'Incorrect endpoint on atproto_pds service', + ) + } + const signingKey = await ctx.actorStore.keypair(requester) + if (op.verificationMethods['atproto'] !== signingKey.did()) { + throw new InvalidRequestError('Incorrect signing key') + } + const account = await ctx.accountManager.getAccount(requester, { + includeDeactivated: true, + }) + if ( + account?.handle && + op.alsoKnownAs.at(0) !== `at://${account.handle}` + ) { + throw new InvalidRequestError('Incorrect handle in alsoKnownAs') + } + + await ctx.plcClient.sendOperation(requester, op) + }, + }) +} diff --git a/packages/pds/src/api/com/atproto/identity/updateHandle.ts b/packages/pds/src/api/com/atproto/identity/updateHandle.ts index 32018601756..eb1bce9593c 100644 --- a/packages/pds/src/api/com/atproto/identity/updateHandle.ts +++ b/packages/pds/src/api/com/atproto/identity/updateHandle.ts @@ -42,7 +42,9 @@ export default function (server: Server, ctx: AppContext) { }) // Pessimistic check to handle spam: also enforced by updateHandle() and the db. - const account = await ctx.accountManager.getAccount(handle) + const account = await ctx.accountManager.getAccount(handle, { + includeDeactivated: true, + }) if (account) { if (account.did !== requester) { diff --git a/packages/pds/src/api/com/atproto/repo/applyWrites.ts b/packages/pds/src/api/com/atproto/repo/applyWrites.ts index 1fd1bbb531e..16f620a30fc 100644 --- a/packages/pds/src/api/com/atproto/repo/applyWrites.ts +++ b/packages/pds/src/api/com/atproto/repo/applyWrites.ts @@ -48,11 +48,17 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ input, auth }) => { const tx = input.body const { repo, validate, swapCommit } = tx - const did = await ctx.accountManager.getDidForActor(repo) + const account = await ctx.accountManager.getAccount(repo, { + includeDeactivated: true, + }) - if (!did) { + if (!account) { throw new InvalidRequestError(`Could not find repo: ${repo}`) + } else if (account.deactivatedAt) { + throw new InvalidRequestError('Account is deactivated') } + + const did = account.did if (did !== auth.credentials.did) { throw new AuthRequiredError() } diff --git a/packages/pds/src/api/com/atproto/repo/createRecord.ts b/packages/pds/src/api/com/atproto/repo/createRecord.ts index 8d7aaedd11c..7e3aead0c56 100644 --- a/packages/pds/src/api/com/atproto/repo/createRecord.ts +++ b/packages/pds/src/api/com/atproto/repo/createRecord.ts @@ -28,11 +28,16 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ input, auth }) => { const { repo, collection, rkey, record, swapCommit, validate } = input.body - const did = await ctx.accountManager.getDidForActor(repo) + const account = await ctx.accountManager.getAccount(repo, { + includeDeactivated: true, + }) - if (!did) { + if (!account) { throw new InvalidRequestError(`Could not find repo: ${repo}`) + } else if (account.deactivatedAt) { + throw new InvalidRequestError('Account is deactivated') } + const did = account.did if (did !== auth.credentials.did) { throw new AuthRequiredError() } diff --git a/packages/pds/src/api/com/atproto/repo/deleteRecord.ts b/packages/pds/src/api/com/atproto/repo/deleteRecord.ts index 5a98c0d9963..51589e9d2bc 100644 --- a/packages/pds/src/api/com/atproto/repo/deleteRecord.ts +++ b/packages/pds/src/api/com/atproto/repo/deleteRecord.ts @@ -22,11 +22,16 @@ export default function (server: Server, ctx: AppContext) { ], handler: async ({ input, auth }) => { const { repo, collection, rkey, swapCommit, swapRecord } = input.body - const did = await ctx.accountManager.getDidForActor(repo) + const account = await ctx.accountManager.getAccount(repo, { + includeDeactivated: true, + }) - if (!did) { + if (!account) { throw new InvalidRequestError(`Could not find repo: ${repo}`) + } else if (account.deactivatedAt) { + throw new InvalidRequestError('Account is deactivated') } + const did = account.did if (did !== auth.credentials.did) { throw new AuthRequiredError() } diff --git a/packages/pds/src/api/com/atproto/repo/importRepo.ts b/packages/pds/src/api/com/atproto/repo/importRepo.ts new file mode 100644 index 00000000000..1e320c99088 --- /dev/null +++ b/packages/pds/src/api/com/atproto/repo/importRepo.ts @@ -0,0 +1,135 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { ActorStoreTransactor } from '../../../../actor-store' +import { TID } from '@atproto/common' +import { + Repo, + WriteOpAction, + getAndParseRecord, + readCarStream, + verifyDiff, +} from '@atproto/repo' +import { InvalidRequestError } from '@atproto/xrpc-server' +import { CID } from 'multiformats/cid' +import PQueue from 'p-queue' +import { AtUri } from '@atproto/syntax' +import { BlobRef, LexValue, RepoRecord } from '@atproto/lexicon' + +export default function (server: Server, ctx: AppContext) { + server.com.atproto.repo.importRepo({ + auth: ctx.authVerifier.accessNotAppPassword, + handler: async ({ input, auth }) => { + const did = auth.credentials.did + if (!ctx.cfg.service.acceptingImports) { + throw new InvalidRequestError('Service is not accepting repo imports') + } + await ctx.actorStore.transact(did, (store) => + importRepo(store, input.body), + ) + }, + }) +} + +const importRepo = async ( + actorStore: ActorStoreTransactor, + incomingCar: AsyncIterable, +) => { + const now = new Date().toISOString() + const rev = TID.nextStr() + const did = actorStore.repo.did + + const { roots, blocks } = await readCarStream(incomingCar) + if (roots.length !== 1) { + throw new InvalidRequestError('expected one root') + } + const currRoot = await actorStore.db.db + .selectFrom('repo_root') + .selectAll() + .executeTakeFirst() + const currRepo = currRoot + ? await Repo.load(actorStore.repo.storage, CID.parse(currRoot.cid)) + : null + const diff = await verifyDiff( + currRepo, + blocks, + roots[0], + undefined, + undefined, + { ensureLeaves: false }, + ) + diff.commit.rev = rev + await actorStore.repo.storage.applyCommit(diff.commit, currRepo === null) + const recordQueue = new PQueue({ concurrency: 50 }) + const controller = new AbortController() + for (const write of diff.writes) { + recordQueue + .add( + async () => { + const uri = AtUri.make(did, write.collection, write.rkey) + if (write.action === WriteOpAction.Delete) { + await actorStore.record.deleteRecord(uri) + } else { + let parsedRecord: RepoRecord + try { + const parsed = await getAndParseRecord(blocks, write.cid) + parsedRecord = parsed.record + } catch { + throw new InvalidRequestError( + `Could not parse record at '${write.collection}/${write.rkey}'`, + ) + } + const indexRecord = actorStore.record.indexRecord( + uri, + write.cid, + parsedRecord, + write.action, + rev, + now, + ) + const recordBlobs = findBlobRefs(parsedRecord) + const blobValues = recordBlobs.map((cid) => ({ + recordUri: uri.toString(), + blobCid: cid.ref.toString(), + })) + const indexRecordBlobs = + blobValues.length > 0 + ? actorStore.db.db + .insertInto('record_blob') + .values(blobValues) + .onConflict((oc) => oc.doNothing()) + .execute() + : Promise.resolve() + await Promise.all([indexRecord, indexRecordBlobs]) + } + }, + { signal: controller.signal }, + ) + .catch((err) => controller.abort(err)) + } + await recordQueue.onIdle() + controller.signal.throwIfAborted() +} + +export const findBlobRefs = (val: LexValue, layer = 0): BlobRef[] => { + if (layer > 32) { + return [] + } + // walk arrays + if (Array.isArray(val)) { + return val.flatMap((item) => findBlobRefs(item, layer + 1)) + } + // objects + if (val && typeof val === 'object') { + // convert blobs, leaving the original encoding so that we don't change CIDs on re-encode + if (val instanceof BlobRef) { + return [val] + } + // retain cids & bytes + if (CID.asCID(val) || val instanceof Uint8Array) { + return [] + } + return Object.values(val).flatMap((item) => findBlobRefs(item, layer + 1)) + } + // pass through + return [] +} diff --git a/packages/pds/src/api/com/atproto/repo/index.ts b/packages/pds/src/api/com/atproto/repo/index.ts index ce13a10fe15..5c754064e95 100644 --- a/packages/pds/src/api/com/atproto/repo/index.ts +++ b/packages/pds/src/api/com/atproto/repo/index.ts @@ -8,6 +8,8 @@ import getRecord from './getRecord' import listRecords from './listRecords' import putRecord from './putRecord' import uploadBlob from './uploadBlob' +import listMissingBlobs from './listMissingBlobs' +import importRepo from './importRepo' export default function (server: Server, ctx: AppContext) { applyWrites(server, ctx) @@ -18,4 +20,6 @@ export default function (server: Server, ctx: AppContext) { listRecords(server, ctx) putRecord(server, ctx) uploadBlob(server, ctx) + listMissingBlobs(server, ctx) + importRepo(server, ctx) } diff --git a/packages/pds/src/api/com/atproto/repo/listMissingBlobs.ts b/packages/pds/src/api/com/atproto/repo/listMissingBlobs.ts new file mode 100644 index 00000000000..8bb01cd5585 --- /dev/null +++ b/packages/pds/src/api/com/atproto/repo/listMissingBlobs.ts @@ -0,0 +1,23 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' + +export default function (server: Server, ctx: AppContext) { + server.com.atproto.repo.listMissingBlobs({ + auth: ctx.authVerifier.access, + handler: async ({ auth, params }) => { + const did = auth.credentials.did + const { limit, cursor } = params + const blobs = await ctx.actorStore.read(did, (store) => + store.repo.blob.listMissingBlobs({ limit, cursor }), + ) + + return { + encoding: 'application/json', + body: { + blobs, + cursor: blobs.at(-1)?.cid, + }, + } + }, + }) +} diff --git a/packages/pds/src/api/com/atproto/repo/putRecord.ts b/packages/pds/src/api/com/atproto/repo/putRecord.ts index a0cc047e4ee..97ee3e4819f 100644 --- a/packages/pds/src/api/com/atproto/repo/putRecord.ts +++ b/packages/pds/src/api/com/atproto/repo/putRecord.ts @@ -38,11 +38,16 @@ export default function (server: Server, ctx: AppContext) { swapCommit, swapRecord, } = input.body - const did = await ctx.accountManager.getDidForActor(repo) + const account = await ctx.accountManager.getAccount(repo, { + includeDeactivated: true, + }) - if (!did) { + if (!account) { throw new InvalidRequestError(`Could not find repo: ${repo}`) + } else if (account.deactivatedAt) { + throw new InvalidRequestError('Account is deactivated') } + const did = account.did if (did !== auth.credentials.did) { throw new AuthRequiredError() } diff --git a/packages/pds/src/api/com/atproto/repo/uploadBlob.ts b/packages/pds/src/api/com/atproto/repo/uploadBlob.ts index 4bb536f804e..ca610f79d9d 100644 --- a/packages/pds/src/api/com/atproto/repo/uploadBlob.ts +++ b/packages/pds/src/api/com/atproto/repo/uploadBlob.ts @@ -12,9 +12,35 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ auth, input }) => { const requester = auth.credentials.did - const blob = await ctx.actorStore.transact(requester, (actorTxn) => { - return actorTxn.repo.blob.addUntetheredBlob(input.encoding, input.body) - }) + const blob = await ctx.actorStore.writeNoTransaction( + requester, + async (store) => { + const metadata = await store.repo.blob.uploadBlobAndGetMetadata( + input.encoding, + input.body, + ) + + return store.transact(async (actorTxn) => { + const blobRef = await actorTxn.repo.blob.trackUntetheredBlob( + metadata, + ) + + // make the blob permanent if an associated record is already indexed + const recordsForBlob = await actorTxn.repo.blob.getRecordsForBlob( + blobRef.ref, + ) + if (recordsForBlob.length > 0) { + await actorTxn.repo.blob.verifyBlobAndMakePermanent({ + cid: blobRef.ref, + mimeType: blobRef.mimeType, + constraints: {}, + }) + } + + return blobRef + }) + }, + ) return { encoding: 'application/json', diff --git a/packages/pds/src/api/com/atproto/server/activateAccount.ts b/packages/pds/src/api/com/atproto/server/activateAccount.ts new file mode 100644 index 00000000000..90170084801 --- /dev/null +++ b/packages/pds/src/api/com/atproto/server/activateAccount.ts @@ -0,0 +1,32 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { assertValidDidDocumentForService } from './util' +import { CidSet } from '@atproto/repo' + +export default function (server: Server, ctx: AppContext) { + server.com.atproto.server.activateAccount({ + auth: ctx.authVerifier.accessNotAppPassword, + handler: async ({ auth }) => { + const requester = auth.credentials.did + + await assertValidDidDocumentForService(ctx, requester) + + await ctx.accountManager.activateAccount(requester) + + const commitData = await ctx.actorStore.read(requester, async (store) => { + const root = await store.repo.storage.getRootDetailed() + const blocks = await store.repo.storage.getBlocks([root.cid]) + return { + cid: root.cid, + rev: root.rev, + since: null, + prev: null, + newBlocks: blocks.blocks, + removedCids: new CidSet(), + } + }) + + await ctx.sequencer.sequenceCommit(requester, commitData, []) + }, + }) +} diff --git a/packages/pds/src/api/com/atproto/server/checkAccountStatus.ts b/packages/pds/src/api/com/atproto/server/checkAccountStatus.ts new file mode 100644 index 00000000000..9ceb7dbcbe9 --- /dev/null +++ b/packages/pds/src/api/com/atproto/server/checkAccountStatus.ts @@ -0,0 +1,46 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { isValidDidDocForService } from './util' + +export default function (server: Server, ctx: AppContext) { + server.com.atproto.server.checkAccountStatus({ + auth: ctx.authVerifier.access, + handler: async ({ auth }) => { + const requester = auth.credentials.did + const [ + repoRoot, + repoBlocks, + indexedRecords, + importedBlobs, + expectedBlobs, + ] = await ctx.actorStore.read(requester, async (store) => { + return await Promise.all([ + store.repo.storage.getRootDetailed(), + store.repo.storage.countBlocks(), + store.record.recordCount(), + store.repo.blob.blobCount(), + store.repo.blob.recordBlobCount(), + ]) + }) + const [activated, validDid] = await Promise.all([ + ctx.accountManager.isAccountActivated(requester), + isValidDidDocForService(ctx, requester), + ]) + + return { + encoding: 'application/json', + body: { + activated, + validDid, + repoCommit: repoRoot.cid.toString(), + repoRev: repoRoot.rev, + repoBlocks, + indexedRecords, + privateStateValues: 0, + expectedBlobs, + importedBlobs, + }, + } + }, + }) +} diff --git a/packages/pds/src/api/com/atproto/server/confirmEmail.ts b/packages/pds/src/api/com/atproto/server/confirmEmail.ts index cbe7e5a0f74..b48165b4f70 100644 --- a/packages/pds/src/api/com/atproto/server/confirmEmail.ts +++ b/packages/pds/src/api/com/atproto/server/confirmEmail.ts @@ -9,7 +9,9 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ auth, input, req }) => { const did = auth.credentials.did - const user = await ctx.accountManager.getAccount(did) + const user = await ctx.accountManager.getAccount(did, { + includeDeactivated: true, + }) if (!user) { throw new InvalidRequestError('user not found', 'AccountNotFound') } diff --git a/packages/pds/src/api/com/atproto/server/createAccount.ts b/packages/pds/src/api/com/atproto/server/createAccount.ts index c8f47a0d432..1a9a4ef4570 100644 --- a/packages/pds/src/api/com/atproto/server/createAccount.ts +++ b/packages/pds/src/api/com/atproto/server/createAccount.ts @@ -1,6 +1,6 @@ import { DidDocument, MINUTE, check } from '@atproto/common' import { AtprotoData, ensureAtpDocument } from '@atproto/identity' -import { InvalidRequestError } from '@atproto/xrpc-server' +import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server' import { ExportableKeypair, Keypair, Secp256k1Keypair } from '@atproto/crypto' import * as plc from '@did-plc/lib' import disposable from 'disposable-email' @@ -19,11 +19,21 @@ export default function (server: Server, ctx: AppContext) { durationMs: 5 * MINUTE, points: 100, }, - handler: async ({ input, req }) => { - const { did, handle, email, password, inviteCode, signingKey, plcOp } = - ctx.entrywayAgent - ? await validateInputsForEntrywayPds(ctx, input.body) - : await validateInputsForLocalPds(ctx, input.body) + auth: ctx.authVerifier.userDidAuthOptional, + handler: async ({ input, auth, req }) => { + const requester = auth.credentials?.iss ?? null + const { + did, + handle, + email, + password, + inviteCode, + signingKey, + plcOp, + deactivated, + } = ctx.entrywayAgent + ? await validateInputsForEntrywayPds(ctx, input.body) + : await validateInputsForLocalPds(ctx, input.body, requester) let didDoc: DidDocument | undefined let creds: { accessJwt: string; refreshJwt: string } @@ -54,9 +64,12 @@ export default function (server: Server, ctx: AppContext) { repoCid: commit.cid, repoRev: commit.rev, inviteCode, + deactivated, }) - await ctx.sequencer.sequenceCommit(did, commit, []) + if (!deactivated) { + await ctx.sequencer.sequenceCommit(did, commit, []) + } await ctx.accountManager.updateRepoRoot(did, commit.cid, commit.rev) didDoc = await didDocForSession(ctx, did, true) await ctx.actorStore.clearReservedKeypair(signingKey.did(), did) @@ -135,12 +148,14 @@ const validateInputsForEntrywayPds = async ( inviteCode: undefined, signingKey, plcOp, + deactivated: false, } } const validateInputsForLocalPds = async ( ctx: AppContext, input: CreateAccountInput, + requester: string | null, ) => { const { email, password, inviteCode } = input if (input.plcOp) { @@ -188,14 +203,38 @@ const validateInputsForLocalPds = async ( // determine the did & any plc ops we need to send // if the provided did document is poorly setup, we throw const signingKey = await Secp256k1Keypair.create({ exportable: true }) - const { did, plcOp } = input.did - ? await validateExistingDid(ctx, handle, input.did, signingKey) - : await createDidAndPlcOp(ctx, handle, input, signingKey) - return { did, handle, email, password, inviteCode, signingKey, plcOp } + let did: string + let plcOp: plc.Operation | null + let deactivated = false + if (input.did) { + if (input.did !== requester) { + throw new AuthRequiredError( + `Missing auth to create account with did: ${input.did}`, + ) + } + did = input.did + plcOp = null + deactivated = true + } else { + const formatted = await formatDidAndPlcOp(ctx, handle, input, signingKey) + did = formatted.did + plcOp = formatted.plcOp + } + + return { + did, + handle, + email, + password, + inviteCode, + signingKey, + plcOp, + deactivated, + } } -const createDidAndPlcOp = async ( +const formatDidAndPlcOp = async ( ctx: AppContext, handle: string, input: CreateAccountInput, @@ -224,47 +263,6 @@ const createDidAndPlcOp = async ( plcOp: plcCreate.op, } } - -const validateExistingDid = async ( - ctx: AppContext, - handle: string, - did: string, - signingKey: Keypair, -): Promise<{ - did: string - plcOp: plc.Operation | null -}> => { - // if the user is bringing their own did: - // resolve the user's did doc data, including rotationKeys if did:plc - // determine if we have the capability to make changes to their DID - let atpData: AtprotoData - try { - atpData = await ctx.idResolver.did.resolveAtprotoData(did) - } catch (err) { - throw new InvalidRequestError( - `could not resolve valid DID document :${did}`, - 'UnresolvableDid', - ) - } - validateAtprotoData(atpData, { - handle, - pds: ctx.cfg.service.publicUrl, - signingKey: signingKey.did(), - }) - - if (did.startsWith('did:plc')) { - const data = await ctx.plcClient.getDocumentData(did) - if (!data.rotationKeys.includes(ctx.plcRotationKey.did())) { - throw new InvalidRequestError( - 'PLC DID does not include service rotation key', - 'IncompatibleDidDoc', - ) - } - } - - return { did: did, plcOp: null } -} - const validateAtprotoData = ( data: AtprotoData, expected: { diff --git a/packages/pds/src/api/com/atproto/server/createSession.ts b/packages/pds/src/api/com/atproto/server/createSession.ts index 70e42fdde62..c315f726f3a 100644 --- a/packages/pds/src/api/com/atproto/server/createSession.ts +++ b/packages/pds/src/api/com/atproto/server/createSession.ts @@ -35,8 +35,14 @@ export default function (server: Server, ctx: AppContext) { const identifier = input.body.identifier.toLowerCase() const user = identifier.includes('@') - ? await ctx.accountManager.getAccountByEmail(identifier, true) - : await ctx.accountManager.getAccount(identifier, true) + ? await ctx.accountManager.getAccountByEmail(identifier, { + includeDeactivated: true, + includeTakenDown: true, + }) + : await ctx.accountManager.getAccount(identifier, { + includeDeactivated: true, + includeTakenDown: true, + }) if (!user) { throw new AuthRequiredError('Invalid identifier or password') diff --git a/packages/pds/src/api/com/atproto/server/deactivateAccount.ts b/packages/pds/src/api/com/atproto/server/deactivateAccount.ts new file mode 100644 index 00000000000..1b7b9179d9a --- /dev/null +++ b/packages/pds/src/api/com/atproto/server/deactivateAccount.ts @@ -0,0 +1,15 @@ +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' + +export default function (server: Server, ctx: AppContext) { + server.com.atproto.server.deactivateAccount({ + auth: ctx.authVerifier.accessNotAppPassword, + handler: async ({ auth, input }) => { + const requester = auth.credentials.did + await ctx.accountManager.deactivateAccount( + requester, + input.body.deleteAfter ?? null, + ) + }, + }) +} diff --git a/packages/pds/src/api/com/atproto/server/deleteAccount.ts b/packages/pds/src/api/com/atproto/server/deleteAccount.ts index 9dc1de0df07..f4c3f59c170 100644 --- a/packages/pds/src/api/com/atproto/server/deleteAccount.ts +++ b/packages/pds/src/api/com/atproto/server/deleteAccount.ts @@ -13,7 +13,10 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ input, req }) => { const { did, password, token } = input.body - const account = await ctx.accountManager.getAccount(did, true) + const account = await ctx.accountManager.getAccount(did, { + includeDeactivated: true, + includeTakenDown: true, + }) if (!account) { throw new InvalidRequestError('account not found') } diff --git a/packages/pds/src/api/com/atproto/server/getServiceAuth.ts b/packages/pds/src/api/com/atproto/server/getServiceAuth.ts new file mode 100644 index 00000000000..52bb0ff5ae9 --- /dev/null +++ b/packages/pds/src/api/com/atproto/server/getServiceAuth.ts @@ -0,0 +1,24 @@ +import { createServiceJwt } from '@atproto/xrpc-server' +import AppContext from '../../../../context' +import { Server } from '../../../../lexicon' + +export default function (server: Server, ctx: AppContext) { + server.com.atproto.server.getServiceAuth({ + auth: ctx.authVerifier.access, + handler: async ({ params, auth }) => { + const did = auth.credentials.did + const keypair = await ctx.actorStore.keypair(did) + const token = await createServiceJwt({ + iss: did, + aud: params.aud, + keypair, + }) + return { + encoding: 'application/json', + body: { + token, + }, + } + }, + }) +} diff --git a/packages/pds/src/api/com/atproto/server/index.ts b/packages/pds/src/api/com/atproto/server/index.ts index f5ab1245c1c..7208f106b17 100644 --- a/packages/pds/src/api/com/atproto/server/index.ts +++ b/packages/pds/src/api/com/atproto/server/index.ts @@ -30,6 +30,11 @@ import createAppPassword from './createAppPassword' import listAppPasswords from './listAppPasswords' import revokeAppPassword from './revokeAppPassword' +import getServiceAuth from './getServiceAuth' +import checkAccountStatus from './checkAccountStatus' +import activateAccount from './activateAccount' +import deactivateAccount from './deactivateAccount' + export default function (server: Server, ctx: AppContext) { describeServer(server, ctx) createAccount(server, ctx) @@ -52,4 +57,8 @@ export default function (server: Server, ctx: AppContext) { createAppPassword(server, ctx) listAppPasswords(server, ctx) revokeAppPassword(server, ctx) + getServiceAuth(server, ctx) + checkAccountStatus(server, ctx) + activateAccount(server, ctx) + deactivateAccount(server, ctx) } diff --git a/packages/pds/src/api/com/atproto/server/refreshSession.ts b/packages/pds/src/api/com/atproto/server/refreshSession.ts index 9b9d4f6a5fd..c33059c44e2 100644 --- a/packages/pds/src/api/com/atproto/server/refreshSession.ts +++ b/packages/pds/src/api/com/atproto/server/refreshSession.ts @@ -11,7 +11,10 @@ export default function (server: Server, ctx: AppContext) { auth: ctx.authVerifier.refresh, handler: async ({ auth, req }) => { const did = auth.credentials.did - const user = await ctx.accountManager.getAccount(did, true) + const user = await ctx.accountManager.getAccount(did, { + includeDeactivated: true, + includeTakenDown: true, + }) if (!user) { throw new InvalidRequestError( `Could not find user info for account: ${did}`, diff --git a/packages/pds/src/api/com/atproto/server/requestAccountDelete.ts b/packages/pds/src/api/com/atproto/server/requestAccountDelete.ts index e6ab60ce74b..5572c4c70d4 100644 --- a/packages/pds/src/api/com/atproto/server/requestAccountDelete.ts +++ b/packages/pds/src/api/com/atproto/server/requestAccountDelete.ts @@ -21,7 +21,10 @@ export default function (server: Server, ctx: AppContext) { auth: ctx.authVerifier.accessCheckTakedown, handler: async ({ auth, req }) => { const did = auth.credentials.did - const account = await ctx.accountManager.getAccount(did) + const account = await ctx.accountManager.getAccount(did, { + includeDeactivated: true, + includeTakenDown: true, + }) if (!account) { throw new InvalidRequestError('account not found') } diff --git a/packages/pds/src/api/com/atproto/server/requestEmailConfirmation.ts b/packages/pds/src/api/com/atproto/server/requestEmailConfirmation.ts index ba031efab3f..1840f09c740 100644 --- a/packages/pds/src/api/com/atproto/server/requestEmailConfirmation.ts +++ b/packages/pds/src/api/com/atproto/server/requestEmailConfirmation.ts @@ -21,7 +21,10 @@ export default function (server: Server, ctx: AppContext) { auth: ctx.authVerifier.accessCheckTakedown, handler: async ({ auth, req }) => { const did = auth.credentials.did - const account = await ctx.accountManager.getAccount(did) + const account = await ctx.accountManager.getAccount(did, { + includeDeactivated: true, + includeTakenDown: true, + }) if (!account) { throw new InvalidRequestError('account not found') } diff --git a/packages/pds/src/api/com/atproto/server/requestEmailUpdate.ts b/packages/pds/src/api/com/atproto/server/requestEmailUpdate.ts index a2f7675576d..0972df7b77e 100644 --- a/packages/pds/src/api/com/atproto/server/requestEmailUpdate.ts +++ b/packages/pds/src/api/com/atproto/server/requestEmailUpdate.ts @@ -21,7 +21,10 @@ export default function (server: Server, ctx: AppContext) { auth: ctx.authVerifier.accessCheckTakedown, handler: async ({ auth, req }) => { const did = auth.credentials.did - const account = await ctx.accountManager.getAccount(did) + const account = await ctx.accountManager.getAccount(did, { + includeDeactivated: true, + includeTakenDown: true, + }) if (!account) { throw new InvalidRequestError('account not found') } diff --git a/packages/pds/src/api/com/atproto/server/requestPasswordReset.ts b/packages/pds/src/api/com/atproto/server/requestPasswordReset.ts index 0248f923f4c..d7684742ba8 100644 --- a/packages/pds/src/api/com/atproto/server/requestPasswordReset.ts +++ b/packages/pds/src/api/com/atproto/server/requestPasswordReset.ts @@ -19,7 +19,10 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ input, req }) => { const email = input.body.email.toLowerCase() - const account = await ctx.accountManager.getAccountByEmail(email) + const account = await ctx.accountManager.getAccountByEmail(email, { + includeDeactivated: true, + includeTakenDown: true, + }) if (!account?.email) { if (ctx.entrywayAgent) { diff --git a/packages/pds/src/api/com/atproto/server/updateEmail.ts b/packages/pds/src/api/com/atproto/server/updateEmail.ts index db6e3fcc25e..33de0ca6f61 100644 --- a/packages/pds/src/api/com/atproto/server/updateEmail.ts +++ b/packages/pds/src/api/com/atproto/server/updateEmail.ts @@ -16,7 +16,9 @@ export default function (server: Server, ctx: AppContext) { 'This email address is not supported, please use a different email.', ) } - const account = await ctx.accountManager.getAccount(did) + const account = await ctx.accountManager.getAccount(did, { + includeDeactivated: true, + }) if (!account) { throw new InvalidRequestError('account not found') } diff --git a/packages/pds/src/api/com/atproto/server/util.ts b/packages/pds/src/api/com/atproto/server/util.ts index fc3bfae8e05..43ec0398531 100644 --- a/packages/pds/src/api/com/atproto/server/util.ts +++ b/packages/pds/src/api/com/atproto/server/util.ts @@ -3,6 +3,8 @@ import { DidDocument } from '@atproto/identity' import { ServerConfig } from '../../../../config' import AppContext from '../../../../context' import { dbLogger } from '../../../../logger' +import { InvalidRequestError } from '@atproto/xrpc-server' +import { getPdsEndpoint, getSigningDidKey } from '@atproto/common' // generate an invite code preceded by the hostname // with '.'s replaced by '-'s so it is not mistakable for a link @@ -40,3 +42,71 @@ export const didDocForSession = async ( dbLogger.warn({ err, did }, 'failed to resolve did doc') } } + +export const isValidDidDocForService = async ( + ctx: AppContext, + did: string, +): Promise => { + try { + await assertValidDidDocumentForService(ctx, did) + return true + } catch { + return false + } +} + +export const assertValidDidDocumentForService = async ( + ctx: AppContext, + did: string, +) => { + if (did.startsWith('did:plc')) { + const resolved = await ctx.plcClient.getDocumentData(did) + await assertValidDocContents(ctx, did, { + pdsEndpoint: resolved.services['atproto_pds']?.endpoint, + signingKey: resolved.verificationMethods['atproto'], + rotationKeys: resolved.rotationKeys, + }) + } else { + const resolved = await ctx.idResolver.did.resolve(did, true) + if (!resolved) { + throw new InvalidRequestError('Could not resolve DID') + } + await assertValidDocContents(ctx, did, { + pdsEndpoint: getPdsEndpoint(resolved), + signingKey: getSigningDidKey(resolved), + }) + } +} + +const assertValidDocContents = async ( + ctx: AppContext, + did: string, + contents: { + signingKey?: string + pdsEndpoint?: string + rotationKeys?: string[] + }, +) => { + const { signingKey, pdsEndpoint, rotationKeys } = contents + + const plcRotationKey = + ctx.cfg.entryway?.plcRotationKey ?? ctx.plcRotationKey.did() + if (rotationKeys !== undefined && !rotationKeys.includes(plcRotationKey)) { + throw new InvalidRequestError( + 'Server rotation key not included in PLC DID data', + ) + } + + if (!pdsEndpoint || pdsEndpoint !== ctx.cfg.service.publicUrl) { + throw new InvalidRequestError( + 'DID document atproto_pds service endpoint does not match PDS public url', + ) + } + + const keypair = await ctx.actorStore.keypair(did) + if (!signingKey || signingKey !== keypair.did()) { + throw new InvalidRequestError( + 'DID document verification method does not match expected signing key', + ) + } +} diff --git a/packages/pds/src/api/com/atproto/sync/listRepos.ts b/packages/pds/src/api/com/atproto/sync/listRepos.ts index 8a9fe8170a4..5641c8cfa68 100644 --- a/packages/pds/src/api/com/atproto/sync/listRepos.ts +++ b/packages/pds/src/api/com/atproto/sync/listRepos.ts @@ -13,6 +13,7 @@ export default function (server: Server, ctx: AppContext) { .selectFrom('actor') .innerJoin('repo_root', 'repo_root.did', 'actor.did') .where(notSoftDeletedClause(ref('actor'))) + .where('actor.deactivatedAt', 'is', null) .select([ 'actor.did as did', 'repo_root.cid as head', diff --git a/packages/pds/src/api/com/atproto/temp/importRepo.ts b/packages/pds/src/api/com/atproto/temp/importRepo.ts deleted file mode 100644 index ff11dd5f6d1..00000000000 --- a/packages/pds/src/api/com/atproto/temp/importRepo.ts +++ /dev/null @@ -1,243 +0,0 @@ -import { Readable } from 'stream' -import assert from 'assert' -import PQueue from 'p-queue' -import axios from 'axios' -import { CID } from 'multiformats/cid' -import { InvalidRequestError } from '@atproto/xrpc-server' -import { AsyncBuffer, TID, wait } from '@atproto/common' -import { AtUri } from '@atproto/syntax' -import { - Repo, - WriteOpAction, - getAndParseRecord, - readCarStream, - verifyDiff, -} from '@atproto/repo' -import { BlobRef, LexValue, RepoRecord } from '@atproto/lexicon' -import { Server } from '../../../../lexicon' -import AppContext from '../../../../context' -import { ActorStoreTransactor } from '../../../../actor-store' -import { AtprotoData } from '@atproto/identity' - -export default function (server: Server, ctx: AppContext) { - server.com.atproto.temp.importRepo({ - opts: { - blobLimit: 5 * 1024 * 1024 * 1024, // 5GB - }, - auth: ctx.authVerifier.role, - handler: async ({ params, input, req }) => { - const { did } = params - const outBuffer = new AsyncBuffer() - sendTicks(outBuffer).catch((err) => { - req.log.error({ err }, 'failed to send ticks') - }) - processImport(ctx, did, input.body, outBuffer).catch(async (err) => { - req.log.error({ did, err }, 'failed import') - try { - await ctx.actorStore.destroy(did) - } catch (err) { - req.log.error({ did, err }, 'failed to clean up actor store') - } - outBuffer.throw(err) - }) - - return { - encoding: 'text/plain', - body: Readable.from(outBuffer.events()), - } - }, - }) -} - -const sendTicks = async (outBuffer: AsyncBuffer) => { - while (!outBuffer.isClosed) { - outBuffer.push('tick\n') - await wait(1000) - } -} - -const processImport = async ( - ctx: AppContext, - did: string, - incomingCar: AsyncIterable, - outBuffer: AsyncBuffer, -) => { - const didData = await ctx.idResolver.did.resolveAtprotoData(did) - const alreadyExists = await ctx.actorStore.exists(did) - if (!alreadyExists) { - const keypair = await ctx.actorStore.getReservedKeypair(did) - if (!keypair) { - throw new InvalidRequestError('No signing key reserved') - } - await ctx.actorStore.create(did, keypair) - } - await ctx.actorStore.transact(did, async (actorStore) => { - const blobRefs = await importRepo(actorStore, incomingCar, outBuffer) - await importBlobs(actorStore, didData, blobRefs, outBuffer) - }) - outBuffer.close() -} - -const importRepo = async ( - actorStore: ActorStoreTransactor, - incomingCar: AsyncIterable, - outBuffer: AsyncBuffer, -) => { - const now = new Date().toISOString() - const rev = TID.nextStr() - const did = actorStore.repo.did - - const { roots, blocks } = await readCarStream(incomingCar) - if (roots.length !== 1) { - throw new InvalidRequestError('expected one root') - } - outBuffer.push(`read ${blocks.size} blocks\n`) - const currRoot = await actorStore.db.db - .selectFrom('repo_root') - .selectAll() - .executeTakeFirst() - const currRepo = currRoot - ? await Repo.load(actorStore.repo.storage, CID.parse(currRoot.cid)) - : null - const diff = await verifyDiff( - currRepo, - blocks, - roots[0], - undefined, - undefined, - { ensureLeaves: false }, - ) - outBuffer.push(`diffed repo and found ${diff.writes.length} writes\n`) - diff.commit.rev = rev - await actorStore.repo.storage.applyCommit(diff.commit, currRepo === null) - const recordQueue = new PQueue({ concurrency: 50 }) - let blobRefs: BlobRef[] = [] - let count = 0 - for (const write of diff.writes) { - recordQueue.add(async () => { - const uri = AtUri.make(did, write.collection, write.rkey) - if (write.action === WriteOpAction.Delete) { - await actorStore.record.deleteRecord(uri) - } else { - let parsedRecord: RepoRecord | null - try { - const parsed = await getAndParseRecord(blocks, write.cid) - parsedRecord = parsed.record - } catch { - parsedRecord = null - } - const indexRecord = actorStore.record.indexRecord( - uri, - write.cid, - parsedRecord, - write.action, - rev, - now, - ) - const recordBlobs = findBlobRefs(parsedRecord) - blobRefs = blobRefs.concat(recordBlobs) - const blobValues = recordBlobs.map((cid) => ({ - recordUri: uri.toString(), - blobCid: cid.ref.toString(), - })) - const indexRecordBlobs = - blobValues.length > 0 - ? actorStore.db.db - .insertInto('record_blob') - .values(blobValues) - .onConflict((oc) => oc.doNothing()) - .execute() - : Promise.resolve() - await Promise.all([indexRecord, indexRecordBlobs]) - } - count++ - if (count % 50 === 0) { - outBuffer.push(`indexed ${count}/${diff.writes.length} writes\n`) - } - }) - } - outBuffer.push(`indexed ${count}/${diff.writes.length} writes\n`) - await recordQueue.onIdle() - return blobRefs -} - -const importBlobs = async ( - actorStore: ActorStoreTransactor, - didData: AtprotoData, - blobRefs: BlobRef[], - outBuffer: AsyncBuffer, -) => { - let blobCount = 0 - const blobQueue = new PQueue({ concurrency: 10 }) - outBuffer.push(`fetching ${blobRefs.length} blobs\n`) - const endpoint = `${didData.pds}/xrpc/com.atproto.sync.getBlob` - for (const ref of blobRefs) { - blobQueue.add(async () => { - try { - await importBlob(actorStore, endpoint, ref) - blobCount++ - outBuffer.push(`imported ${blobCount}/${blobRefs.length} blobs\n`) - } catch (err) { - outBuffer.push(`failed to import blob: ${ref.ref.toString()}\n`) - } - }) - } - await blobQueue.onIdle() - outBuffer.push(`finished importing all blobs\n`) -} - -const importBlob = async ( - actorStore: ActorStoreTransactor, - endpoint: string, - blob: BlobRef, -) => { - const hasBlob = await actorStore.db.db - .selectFrom('blob') - .selectAll() - .where('cid', '=', blob.ref.toString()) - .executeTakeFirst() - if (hasBlob) { - return - } - const res = await axios.get(endpoint, { - params: { did: actorStore.repo.did, cid: blob.ref.toString() }, - decompress: true, - responseType: 'stream', - timeout: 5000, - }) - const mimeType = res.headers['content-type'] ?? 'application/octet-stream' - const importedRef = await actorStore.repo.blob.addUntetheredBlob( - mimeType, - res.data, - ) - assert(blob.ref.equals(importedRef.ref)) - await actorStore.repo.blob.verifyBlobAndMakePermanent({ - mimeType: blob.mimeType, - cid: blob.ref, - constraints: {}, - }) -} - -export const findBlobRefs = (val: LexValue, layer = 0): BlobRef[] => { - if (layer > 10) { - return [] - } - // walk arrays - if (Array.isArray(val)) { - return val.flatMap((item) => findBlobRefs(item, layer + 1)) - } - // objects - if (val && typeof val === 'object') { - // convert blobs, leaving the original encoding so that we don't change CIDs on re-encode - if (val instanceof BlobRef) { - return [val] - } - // retain cids & bytes - if (CID.asCID(val) || val instanceof Uint8Array) { - return [] - } - return Object.values(val).flatMap((item) => findBlobRefs(item, layer + 1)) - } - // pass through - return [] -} diff --git a/packages/pds/src/api/com/atproto/temp/index.ts b/packages/pds/src/api/com/atproto/temp/index.ts index dbf249345f3..a39aef98fe9 100644 --- a/packages/pds/src/api/com/atproto/temp/index.ts +++ b/packages/pds/src/api/com/atproto/temp/index.ts @@ -1,13 +1,7 @@ import AppContext from '../../../../context' import { Server } from '../../../../lexicon' import checkSignupQueue from './checkSignupQueue' -import importRepo from './importRepo' -import pushBlob from './pushBlob' -import transferAccount from './transferAccount' export default function (server: Server, ctx: AppContext) { checkSignupQueue(server, ctx) - importRepo(server, ctx) - pushBlob(server, ctx) - transferAccount(server, ctx) } diff --git a/packages/pds/src/api/com/atproto/temp/pushBlob.ts b/packages/pds/src/api/com/atproto/temp/pushBlob.ts deleted file mode 100644 index 74ef80e42c0..00000000000 --- a/packages/pds/src/api/com/atproto/temp/pushBlob.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Server } from '../../../../lexicon' -import AppContext from '../../../../context' - -export default function (server: Server, ctx: AppContext) { - server.com.atproto.temp.pushBlob({ - auth: ctx.authVerifier.role, - handler: async ({ params, input }) => { - const { did } = params - - await ctx.actorStore.transact(did, async (actorTxn) => { - const blob = await actorTxn.repo.blob.addUntetheredBlob( - input.encoding, - input.body, - ) - await actorTxn.repo.blob.verifyBlobAndMakePermanent({ - mimeType: blob.mimeType, - cid: blob.ref, - constraints: {}, - }) - }) - }, - }) -} diff --git a/packages/pds/src/api/com/atproto/temp/transferAccount.ts b/packages/pds/src/api/com/atproto/temp/transferAccount.ts deleted file mode 100644 index 0b1b765089c..00000000000 --- a/packages/pds/src/api/com/atproto/temp/transferAccount.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { ensureAtpDocument } from '@atproto/identity' -import * as plc from '@did-plc/lib' -import { Server } from '../../../../lexicon' -import AppContext from '../../../../context' -import { check, cidForCbor } from '@atproto/common' -import { InvalidRequestError } from '@atproto/xrpc-server' -import { BlockMap, CidSet } from '@atproto/repo' - -export default function (server: Server, ctx: AppContext) { - server.com.atproto.temp.transferAccount({ - auth: ctx.authVerifier.role, - handler: async ({ input }) => { - const { did, handle } = input.body - - const signingKey = await ctx.actorStore.keypair(did) - const currRoot = await ctx.actorStore.read(did, (store) => - store.repo.storage.getRootDetailed(), - ) - - const plcOp = did.startsWith('did:plc') - ? await verifyDidAndPlcOp( - ctx, - did, - handle, - signingKey.did(), - input.body.plcOp, - ) - : null - - const { accessJwt, refreshJwt } = await ctx.accountManager.createAccount({ - did, - handle, - repoCid: currRoot.cid, - repoRev: currRoot.rev, - }) - - if (plcOp) { - try { - await ctx.plcClient.sendOperation(did, plcOp) - } catch (err) { - await ctx.accountManager.deleteAccount(did) - throw err - } - } - - await ctx.sequencer.sequenceCommit( - did, - { - cid: currRoot.cid, - rev: currRoot.rev, - since: null, - prev: null, - newBlocks: new BlockMap(), - removedCids: new CidSet(), - }, - [], - ) - - return { - encoding: 'application/json', - body: { - handle, - did: did, - accessJwt: accessJwt, - refreshJwt: refreshJwt, - }, - } - }, - }) -} - -const verifyDidAndPlcOp = async ( - ctx: AppContext, - did: string, - handle: string, - signingKey: string, - plcOp: unknown, -): Promise => { - if (!check.is(plcOp, plc.def.operation)) { - throw new InvalidRequestError('invalid plc operation', 'IncompatibleDidDoc') - } - await plc.assureValidOp(plcOp) - const prev = await ctx.plcClient.getLastOp(did) - if (!prev || prev.type === 'plc_tombstone') { - throw new InvalidRequestError( - 'no accessible prev for did', - 'IncompatibleDidDoc', - ) - } - const prevCid = await cidForCbor(prev) - if (plcOp.prev?.toString() !== prevCid.toString()) { - throw new InvalidRequestError( - 'invalid prev on plc operation', - 'IncompatibleDidDoc', - ) - } - const normalizedPrev = plc.normalizeOp(prev) - await plc.assureValidSig(normalizedPrev.rotationKeys, plcOp) - const doc = plc.formatDidDoc({ did, ...plcOp }) - const data = ensureAtpDocument(doc) - if (handle !== data.handle) { - throw new InvalidRequestError( - 'invalid handle on plc operation', - 'IncompatibleDidDoc', - ) - } else if (data.pds !== ctx.cfg.service.publicUrl) { - throw new InvalidRequestError( - 'invalid service on plc operation', - 'IncompatibleDidDoc', - ) - } else if (data.signingKey !== signingKey) { - throw new InvalidRequestError( - 'invalid signing key on plc operation', - 'IncompatibleDidDoc', - ) - } - return plcOp -} diff --git a/packages/pds/src/auth-verifier.ts b/packages/pds/src/auth-verifier.ts index b24f530ade3..9d1f7b8bd19 100644 --- a/packages/pds/src/auth-verifier.ts +++ b/packages/pds/src/auth-verifier.ts @@ -73,6 +73,14 @@ type RefreshOutput = { artifacts: string } +type UserDidOutput = { + credentials: { + type: 'user_did' + aud: string + iss: string + } +} + type ValidatedBearer = { did: string scope: AuthScope @@ -126,7 +134,9 @@ export class AuthVerifier { AuthScope.Access, AuthScope.AppPass, ]) - const found = await this.accountManager.getAccount(result.credentials.did) + const found = await this.accountManager.getAccount(result.credentials.did, { + includeDeactivated: true, + }) if (!found) { // will be turned into ExpiredToken for the client if proxied by entryway throw new ForbiddenError('Account not found', 'AccountNotFound') @@ -219,24 +229,35 @@ export class AuthVerifier { } } - adminService = async (reqCtx: ReqCtx): Promise => { - const jwtStr = bearerTokenFromReq(reqCtx.req) - if (!jwtStr) { - throw new AuthRequiredError('missing jwt', 'MissingJwt') - } - const payload = await verifyServiceJwt( - jwtStr, - this.dids.entryway ?? this.dids.pds, - async (did, forceRefresh) => { - if (did !== this.dids.admin) { - throw new AuthRequiredError( - 'Untrusted issuer for admin actions', - 'UntrustedIss', - ) - } - return this.idResolver.did.resolveAtprotoKey(did, forceRefresh) + userDidAuth = async (reqCtx: ReqCtx): Promise => { + const payload = await this.verifyServiceJwt(reqCtx, { + aud: this.dids.entryway ?? this.dids.pds, + iss: null, + }) + return { + credentials: { + type: 'user_did', + aud: payload.aud, + iss: payload.iss, }, - ) + } + } + + userDidAuthOptional = async ( + reqCtx: ReqCtx, + ): Promise => { + if (isBearerToken(reqCtx.req)) { + return await this.userDidAuth(reqCtx) + } else { + return { credentials: null } + } + } + + adminService = async (reqCtx: ReqCtx): Promise => { + const payload = await this.verifyServiceJwt(reqCtx, { + aud: this.dids.entryway ?? this.dids.pds, + iss: [this.dids.admin], + }) return { credentials: { type: 'service', @@ -308,6 +329,28 @@ export class AuthVerifier { } } + async verifyServiceJwt( + reqCtx: ReqCtx, + opts: { aud: string | null; iss: string[] | null }, + ) { + const getSigningKey = async ( + did: string, + forceRefresh: boolean, + ): Promise => { + if (opts.iss !== null && !opts.iss.includes(did)) { + throw new AuthRequiredError('Untrusted issuer', 'UntrustedIss') + } + return this.idResolver.did.resolveAtprotoKey(did, forceRefresh) + } + + const jwtStr = bearerTokenFromReq(reqCtx.req) + if (!jwtStr) { + throw new AuthRequiredError('missing jwt', 'MissingJwt') + } + const payload = await verifyServiceJwt(jwtStr, opts.aud, getSigningKey) + return { iss: payload.iss, aud: payload.aud } + } + parseRoleCreds(req: express.Request) { const parsed = parseBasicAuth(req.headers.authorization || '') const { Missing, Valid, Invalid } = RoleStatus diff --git a/packages/pds/src/config/config.ts b/packages/pds/src/config/config.ts index 2f8f295b2b0..34f113df985 100644 --- a/packages/pds/src/config/config.ts +++ b/packages/pds/src/config/config.ts @@ -22,6 +22,7 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => { version: env.version, // default? privacyPolicyUrl: env.privacyPolicyUrl, termsOfServiceUrl: env.termsOfServiceUrl, + acceptingImports: env.acceptingImports ?? true, } const dbLoc = (name: string) => { @@ -245,6 +246,7 @@ export type ServiceConfig = { publicUrl: string did: string version?: string + acceptingImports: boolean privacyPolicyUrl?: string termsOfServiceUrl?: string } diff --git a/packages/pds/src/config/env.ts b/packages/pds/src/config/env.ts index cc5f698fa80..d096cda994e 100644 --- a/packages/pds/src/config/env.ts +++ b/packages/pds/src/config/env.ts @@ -9,6 +9,7 @@ export const readEnv = (): ServerEnvironment => { version: envStr('PDS_VERSION'), privacyPolicyUrl: envStr('PDS_PRIVACY_POLICY_URL'), termsOfServiceUrl: envStr('PDS_TERMS_OF_SERVICE_URL'), + acceptingImports: envBool('PDS_ACCEPTING_REPO_IMPORTS'), // database dataDirectory: envStr('PDS_DATA_DIRECTORY'), @@ -110,6 +111,7 @@ export type ServerEnvironment = { version?: string privacyPolicyUrl?: string termsOfServiceUrl?: string + acceptingImports?: boolean // database dataDirectory?: string diff --git a/packages/pds/src/db/util.ts b/packages/pds/src/db/util.ts index 17b84822753..519921c70dc 100644 --- a/packages/pds/src/db/util.ts +++ b/packages/pds/src/db/util.ts @@ -22,6 +22,7 @@ export const softDeleted = (repoOrRecord: { takedownRef: string | null }) => { } export const countAll = sql`count(*)` +export const countDistinct = (ref: DbRef) => sql`count(distinct ${ref})` // For use with doUpdateSet() export const excluded = (db: Kysely, col) => { diff --git a/packages/pds/src/lexicon/index.ts b/packages/pds/src/lexicon/index.ts index ed08ff55702..6b3b6de582a 100644 --- a/packages/pds/src/lexicon/index.ts +++ b/packages/pds/src/lexicon/index.ts @@ -90,10 +90,7 @@ import * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCra import * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos' import * as ComAtprotoTempCheckSignupQueue from './types/com/atproto/temp/checkSignupQueue' import * as ComAtprotoTempFetchLabels from './types/com/atproto/temp/fetchLabels' -import * as ComAtprotoTempImportRepo from './types/com/atproto/temp/importRepo' -import * as ComAtprotoTempPushBlob from './types/com/atproto/temp/pushBlob' import * as ComAtprotoTempRequestPhoneVerification from './types/com/atproto/temp/requestPhoneVerification' -import * as ComAtprotoTempTransferAccount from './types/com/atproto/temp/transferAccount' import * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences' import * as AppBskyActorGetProfile from './types/app/bsky/actor/getProfile' import * as AppBskyActorGetProfiles from './types/app/bsky/actor/getProfiles' @@ -1167,28 +1164,6 @@ export class ComAtprotoTempNS { return this._server.xrpc.method(nsid, cfg) } - importRepo( - cfg: ConfigOf< - AV, - ComAtprotoTempImportRepo.Handler>, - ComAtprotoTempImportRepo.HandlerReqCtx> - >, - ) { - const nsid = 'com.atproto.temp.importRepo' // @ts-ignore - return this._server.xrpc.method(nsid, cfg) - } - - pushBlob( - cfg: ConfigOf< - AV, - ComAtprotoTempPushBlob.Handler>, - ComAtprotoTempPushBlob.HandlerReqCtx> - >, - ) { - const nsid = 'com.atproto.temp.pushBlob' // @ts-ignore - return this._server.xrpc.method(nsid, cfg) - } - requestPhoneVerification( cfg: ConfigOf< AV, @@ -1199,17 +1174,6 @@ export class ComAtprotoTempNS { const nsid = 'com.atproto.temp.requestPhoneVerification' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } - - transferAccount( - cfg: ConfigOf< - AV, - ComAtprotoTempTransferAccount.Handler>, - ComAtprotoTempTransferAccount.HandlerReqCtx> - >, - ) { - const nsid = 'com.atproto.temp.transferAccount' // @ts-ignore - return this._server.xrpc.method(nsid, cfg) - } } export class AppNS { diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index a3491462d50..6164b1706a3 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -4853,59 +4853,6 @@ export const schemaDict = { }, }, }, - ComAtprotoTempImportRepo: { - lexicon: 1, - id: 'com.atproto.temp.importRepo', - defs: { - main: { - type: 'procedure', - description: - "Gets the did's repo, optionally catching up from a specific revision.", - parameters: { - type: 'params', - required: ['did'], - properties: { - did: { - type: 'string', - format: 'did', - description: 'The DID of the repo.', - }, - }, - }, - input: { - encoding: 'application/vnd.ipld.car', - }, - output: { - encoding: 'text/plain', - }, - }, - }, - }, - ComAtprotoTempPushBlob: { - lexicon: 1, - id: 'com.atproto.temp.pushBlob', - defs: { - main: { - type: 'procedure', - description: - "Gets the did's repo, optionally catching up from a specific revision.", - parameters: { - type: 'params', - required: ['did'], - properties: { - did: { - type: 'string', - format: 'did', - description: 'The DID of the repo.', - }, - }, - }, - input: { - encoding: '*/*', - }, - }, - }, - }, ComAtprotoTempRequestPhoneVerification: { lexicon: 1, id: 'com.atproto.temp.requestPhoneVerification', @@ -4929,83 +4876,6 @@ export const schemaDict = { }, }, }, - ComAtprotoTempTransferAccount: { - lexicon: 1, - id: 'com.atproto.temp.transferAccount', - defs: { - main: { - type: 'procedure', - description: - 'Transfer an account. NOTE: temporary method, necessarily how account migration will be implemented.', - input: { - encoding: 'application/json', - schema: { - type: 'object', - required: ['handle', 'did', 'plcOp'], - properties: { - handle: { - type: 'string', - format: 'handle', - }, - did: { - type: 'string', - format: 'did', - }, - plcOp: { - type: 'unknown', - }, - }, - }, - }, - output: { - encoding: 'application/json', - schema: { - type: 'object', - required: ['accessJwt', 'refreshJwt', 'handle', 'did'], - properties: { - accessJwt: { - type: 'string', - }, - refreshJwt: { - type: 'string', - }, - handle: { - type: 'string', - format: 'handle', - }, - did: { - type: 'string', - format: 'did', - }, - }, - }, - }, - errors: [ - { - name: 'InvalidHandle', - }, - { - name: 'InvalidPassword', - }, - { - name: 'InvalidInviteCode', - }, - { - name: 'HandleNotAvailable', - }, - { - name: 'UnsupportedDomain', - }, - { - name: 'UnresolvableDid', - }, - { - name: 'IncompatibleDidDoc', - }, - ], - }, - }, - }, AppBskyActorDefs: { lexicon: 1, id: 'app.bsky.actor.defs', @@ -8984,11 +8854,8 @@ export const ids = { ComAtprotoSyncSubscribeRepos: 'com.atproto.sync.subscribeRepos', ComAtprotoTempCheckSignupQueue: 'com.atproto.temp.checkSignupQueue', ComAtprotoTempFetchLabels: 'com.atproto.temp.fetchLabels', - ComAtprotoTempImportRepo: 'com.atproto.temp.importRepo', - ComAtprotoTempPushBlob: 'com.atproto.temp.pushBlob', ComAtprotoTempRequestPhoneVerification: 'com.atproto.temp.requestPhoneVerification', - ComAtprotoTempTransferAccount: 'com.atproto.temp.transferAccount', AppBskyActorDefs: 'app.bsky.actor.defs', AppBskyActorGetPreferences: 'app.bsky.actor.getPreferences', AppBskyActorGetProfile: 'app.bsky.actor.getProfile', diff --git a/packages/pds/src/lexicon/types/com/atproto/temp/importRepo.ts b/packages/pds/src/lexicon/types/com/atproto/temp/importRepo.ts deleted file mode 100644 index 44bce41481c..00000000000 --- a/packages/pds/src/lexicon/types/com/atproto/temp/importRepo.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * GENERATED CODE - DO NOT MODIFY - */ -import express from 'express' -import stream from 'stream' -import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { lexicons } from '../../../../lexicons' -import { isObj, hasProp } from '../../../../util' -import { CID } from 'multiformats/cid' -import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' - -export interface QueryParams { - /** The DID of the repo. */ - did: string -} - -export type InputSchema = string | Uint8Array - -export interface HandlerInput { - encoding: 'application/vnd.ipld.car' - body: stream.Readable -} - -export interface HandlerSuccess { - encoding: 'text/plain' - body: Uint8Array | stream.Readable - headers?: { [key: string]: string } -} - -export interface HandlerError { - status: number - message?: string -} - -export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough -export type HandlerReqCtx = { - auth: HA - params: QueryParams - input: HandlerInput - req: express.Request - res: express.Response -} -export type Handler = ( - ctx: HandlerReqCtx, -) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/temp/pushBlob.ts b/packages/pds/src/lexicon/types/com/atproto/temp/pushBlob.ts deleted file mode 100644 index d18a60b598f..00000000000 --- a/packages/pds/src/lexicon/types/com/atproto/temp/pushBlob.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * GENERATED CODE - DO NOT MODIFY - */ -import express from 'express' -import stream from 'stream' -import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { lexicons } from '../../../../lexicons' -import { isObj, hasProp } from '../../../../util' -import { CID } from 'multiformats/cid' -import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' - -export interface QueryParams { - /** The DID of the repo. */ - did: string -} - -export type InputSchema = string | Uint8Array - -export interface HandlerInput { - encoding: '*/*' - body: stream.Readable -} - -export interface HandlerError { - status: number - message?: string -} - -export type HandlerOutput = HandlerError | void -export type HandlerReqCtx = { - auth: HA - params: QueryParams - input: HandlerInput - req: express.Request - res: express.Response -} -export type Handler = ( - ctx: HandlerReqCtx, -) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/com/atproto/temp/transferAccount.ts b/packages/pds/src/lexicon/types/com/atproto/temp/transferAccount.ts deleted file mode 100644 index 565150caba2..00000000000 --- a/packages/pds/src/lexicon/types/com/atproto/temp/transferAccount.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * GENERATED CODE - DO NOT MODIFY - */ -import express from 'express' -import { ValidationResult, BlobRef } from '@atproto/lexicon' -import { lexicons } from '../../../../lexicons' -import { isObj, hasProp } from '../../../../util' -import { CID } from 'multiformats/cid' -import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' - -export interface QueryParams {} - -export interface InputSchema { - handle: string - did: string - plcOp: {} - [k: string]: unknown -} - -export interface OutputSchema { - accessJwt: string - refreshJwt: string - handle: string - did: string - [k: string]: unknown -} - -export interface HandlerInput { - encoding: 'application/json' - body: InputSchema -} - -export interface HandlerSuccess { - encoding: 'application/json' - body: OutputSchema - headers?: { [key: string]: string } -} - -export interface HandlerError { - status: number - message?: string - error?: - | 'InvalidHandle' - | 'InvalidPassword' - | 'InvalidInviteCode' - | 'HandleNotAvailable' - | 'UnsupportedDomain' - | 'UnresolvableDid' - | 'IncompatibleDidDoc' -} - -export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough -export type HandlerReqCtx = { - auth: HA - params: QueryParams - input: HandlerInput - req: express.Request - res: express.Response -} -export type Handler = ( - ctx: HandlerReqCtx, -) => Promise | HandlerOutput diff --git a/packages/pds/src/mailer/index.ts b/packages/pds/src/mailer/index.ts index df539ac03b9..f6c57cec40e 100644 --- a/packages/pds/src/mailer/index.ts +++ b/packages/pds/src/mailer/index.ts @@ -53,6 +53,13 @@ export class ServerMailer { }) } + async sendPlcOperation(params: { token: string }, mailOpts: Mail.Options) { + return this.sendTemplate('plcOperation', params, { + subject: 'PLC Update Operation Requested', + ...mailOpts, + }) + } + private async sendTemplate(templateName, params, mailOpts: Mail.Options) { const html = this.templates[templateName]({ ...params, diff --git a/packages/pds/src/mailer/templates.ts b/packages/pds/src/mailer/templates.ts index 08c3f3883fb..86a683053f9 100644 --- a/packages/pds/src/mailer/templates.ts +++ b/packages/pds/src/mailer/templates.ts @@ -5,3 +5,4 @@ export { default as resetPassword } from './templates/reset-password.hbs' export { default as deleteAccount } from './templates/delete-account.hbs' export { default as confirmEmail } from './templates/confirm-email.hbs' export { default as updateEmail } from './templates/update-email.hbs' +export { default as plcOperation } from './templates/plc-operation.hbs' diff --git a/packages/pds/src/mailer/templates/plc-operation.hbs b/packages/pds/src/mailer/templates/plc-operation.hbs new file mode 100644 index 00000000000..2d998245202 --- /dev/null +++ b/packages/pds/src/mailer/templates/plc-operation.hbs @@ -0,0 +1,384 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/pds/src/well-known.ts b/packages/pds/src/well-known.ts index d1c6169cac9..a2c0fe4a7b1 100644 --- a/packages/pds/src/well-known.ts +++ b/packages/pds/src/well-known.ts @@ -14,7 +14,7 @@ export const createRouter = (ctx: AppContext): express.Router => { } let did: string | undefined try { - const user = await ctx.accountManager.getAccount(handle, true) + const user = await ctx.accountManager.getAccount(handle) did = user?.did } catch (err) { return res.status(500).send('Internal Server Error') diff --git a/packages/pds/tests/account-deactivation.test.ts b/packages/pds/tests/account-deactivation.test.ts new file mode 100644 index 00000000000..83820f24e49 --- /dev/null +++ b/packages/pds/tests/account-deactivation.test.ts @@ -0,0 +1,148 @@ +import AtpAgent from '@atproto/api' +import { SeedClient, TestNetworkNoAppView, basicSeed } from '@atproto/dev-env' + +describe('account deactivation', () => { + let network: TestNetworkNoAppView + + let sc: SeedClient + let agent: AtpAgent + + let alice: string + + beforeAll(async () => { + network = await TestNetworkNoAppView.create({ + dbPostgresSchema: 'account_deactivation', + }) + + sc = network.getSeedClient() + agent = network.pds.getClient() + + await basicSeed(sc) + alice = sc.dids.alice + await network.processAll() + }) + + afterAll(async () => { + await network.close() + }) + + it('deactivates account', async () => { + await agent.com.atproto.server.deactivateAccount( + {}, + { encoding: 'application/json', headers: sc.getHeaders(alice) }, + ) + }) + + it('no longer serves repo data', async () => { + await expect( + agent.com.atproto.sync.getRepo({ did: alice }), + ).rejects.toThrow() + await expect( + agent.com.atproto.sync.getLatestCommit({ did: alice }), + ).rejects.toThrow() + await expect( + agent.com.atproto.sync.listBlobs({ did: alice }), + ).rejects.toThrow() + const recordUri = sc.posts[alice][0].ref.uri + await expect( + agent.com.atproto.sync.getRecord({ + did: alice, + collection: recordUri.collection, + rkey: recordUri.rkey, + }), + ).rejects.toThrow() + await expect( + agent.com.atproto.repo.getRecord({ + repo: alice, + collection: recordUri.collection, + rkey: recordUri.rkey, + }), + ).rejects.toThrow() + await expect( + agent.com.atproto.repo.describeRepo({ + repo: alice, + }), + ).rejects.toThrow() + + const blobCid = sc.profiles[alice].avatar.cid + await expect( + agent.com.atproto.sync.getBlob({ + did: alice, + cid: blobCid, + }), + ).rejects.toThrow() + const listedRepos = await agent.com.atproto.sync.listRepos() + expect(listedRepos.data.repos.find((r) => r.did === alice)).toBeUndefined() + }) + + it('no longer resolves handle', async () => { + await expect( + agent.com.atproto.identity.resolveHandle({ + handle: sc.accounts[alice].handle, + }), + ).rejects.toThrow() + }) + + it('still allows login', async () => { + await agent.com.atproto.server.createSession({ + identifier: alice, + password: sc.accounts[alice].password, + }) + }) + + it('does not allow writes', async () => { + const createAttempt = agent.com.atproto.repo.createRecord( + { + repo: alice, + collection: 'app.bsky.feed.post', + record: { + text: 'blah', + createdAt: new Date().toISOString(), + }, + }, + { + encoding: 'application/json', + headers: sc.getHeaders(alice), + }, + ) + const uri = sc.posts[alice][0].ref.uri + await expect(createAttempt).rejects.toThrow('Account is deactivated') + + const putAttempt = agent.com.atproto.repo.putRecord( + { + repo: alice, + collection: uri.collection, + rkey: uri.rkey, + record: { + text: 'blah', + createdAt: new Date().toISOString(), + }, + }, + { + encoding: 'application/json', + headers: sc.getHeaders(alice), + }, + ) + await expect(putAttempt).rejects.toThrow('Account is deactivated') + + const deleteAttempt = agent.com.atproto.repo.deleteRecord( + { + repo: alice, + collection: uri.collection, + rkey: uri.rkey, + }, + { + encoding: 'application/json', + headers: sc.getHeaders(alice), + }, + ) + await expect(deleteAttempt).rejects.toThrow('Account is deactivated') + }) + + it('reactivates', async () => { + await agent.com.atproto.server.activateAccount(undefined, { + headers: sc.getHeaders(alice), + }) + await agent.com.atproto.sync.getRepo({ did: alice }) + }) +}) diff --git a/packages/pds/tests/account-migration.test.ts b/packages/pds/tests/account-migration.test.ts new file mode 100644 index 00000000000..beb14599e38 --- /dev/null +++ b/packages/pds/tests/account-migration.test.ts @@ -0,0 +1,219 @@ +import AtpAgent, { AtUri } from '@atproto/api' +import { + SeedClient, + TestNetworkNoAppView, + TestPds, + mockNetworkUtilities, +} from '@atproto/dev-env' +import { readCar } from '@atproto/repo' +import assert from 'assert' + +describe('account migration', () => { + let network: TestNetworkNoAppView + let newPds: TestPds + + let sc: SeedClient + let oldAgent: AtpAgent + let newAgent: AtpAgent + + let alice: string + + beforeAll(async () => { + network = await TestNetworkNoAppView.create({ + dbPostgresSchema: 'account_migration', + }) + newPds = await TestPds.create({ + didPlcUrl: network.plc.url, + }) + mockNetworkUtilities(newPds) + + sc = network.getSeedClient() + oldAgent = network.pds.getClient() + newAgent = newPds.getClient() + + await sc.createAccount('alice', { + email: 'alice@test.com', + handle: 'alice.test', + password: 'alice-pass', + }) + alice = sc.dids.alice + + for (let i = 0; i < 100; i++) { + await sc.post(alice, 'test post') + } + const img1 = await sc.uploadFile( + alice, + '../dev-env/src/seed/img/at.png', + 'image/png', + ) + const img2 = await sc.uploadFile( + alice, + '../dev-env/src/seed/img/key-alt.jpg', + 'image/jpeg', + ) + const img3 = await sc.uploadFile( + alice, + '../dev-env/src/seed/img/key-landscape-small.jpg', + 'image/jpeg', + ) + + await sc.post(alice, 'test', undefined, [img1]) + await sc.post(alice, 'test', undefined, [img1, img2]) + await sc.post(alice, 'test', undefined, [img3]) + + await network.processAll() + + await oldAgent.login({ + identifier: sc.accounts[alice].handle, + password: sc.accounts[alice].password, + }) + }) + + afterAll(async () => { + await newPds.close() + await network.close() + }) + + it('migrates an account', async () => { + const describeRes = await newAgent.api.com.atproto.server.describeServer() + const newServerDid = describeRes.data.did + + const serviceJwtRes = await oldAgent.com.atproto.server.getServiceAuth({ + aud: newServerDid, + }) + const serviceJwt = serviceJwtRes.data.token + + await newAgent.api.com.atproto.server.createAccount( + { + handle: 'new-alice.test', + email: 'alice@test.com', + password: 'alice-pass', + did: alice, + }, + { + headers: { authorization: `Bearer ${serviceJwt}` }, + encoding: 'application/json', + }, + ) + await newAgent.login({ + identifier: 'new-alice.test', + password: 'alice-pass', + }) + + const statusRes1 = await newAgent.com.atproto.server.checkAccountStatus() + expect(statusRes1.data).toMatchObject({ + activated: false, + validDid: false, + repoBlocks: 2, // commit & empty data root + indexedRecords: 0, + privateStateValues: 0, + expectedBlobs: 0, + importedBlobs: 0, + }) + + const repoRes = await oldAgent.com.atproto.sync.getRepo({ did: alice }) + const carBlocks = await readCar(repoRes.data) + + await newAgent.com.atproto.repo.importRepo(repoRes.data, { + encoding: 'application/vnd.ipld.car', + }) + + const statusRes2 = await newAgent.com.atproto.server.checkAccountStatus() + expect(statusRes2.data).toMatchObject({ + activated: false, + validDid: false, + indexedRecords: 103, + privateStateValues: 0, + expectedBlobs: 3, + importedBlobs: 0, + }) + expect(statusRes2.data.repoBlocks).toBe(carBlocks.blocks.size) + + const missingBlobs = await newAgent.com.atproto.repo.listMissingBlobs() + expect(missingBlobs.data.blobs.length).toBe(3) + + let blobCursor: string | undefined = undefined + do { + const listedBlobs = await oldAgent.com.atproto.sync.listBlobs({ + did: alice, + cursor: blobCursor, + }) + for (const cid of listedBlobs.data.cids) { + const blobRes = await oldAgent.com.atproto.sync.getBlob({ + did: alice, + cid, + }) + await newAgent.com.atproto.repo.uploadBlob(blobRes.data, { + encoding: blobRes.headers['content-type'], + }) + } + blobCursor = listedBlobs.data.cursor + } while (blobCursor) + + const statusRes3 = await newAgent.com.atproto.server.checkAccountStatus() + expect(statusRes3.data.expectedBlobs).toBe(3) + expect(statusRes3.data.importedBlobs).toBe(3) + + const prefs = await oldAgent.api.app.bsky.actor.getPreferences() + await newAgent.api.app.bsky.actor.putPreferences(prefs.data) + + const getDidCredentials = + await newAgent.com.atproto.identity.getRecommendedDidCredentials() + + await oldAgent.com.atproto.identity.requestPlcOperationSignature() + const res = await network.pds.ctx.accountManager.db.db + .selectFrom('email_token') + .selectAll() + .where('did', '=', alice) + .where('purpose', '=', 'plc_operation') + .executeTakeFirst() + const token = res?.token + assert(token) + + const plcOp = await oldAgent.com.atproto.identity.signPlcOperation({ + token, + ...getDidCredentials.data, + }) + + await newAgent.com.atproto.identity.submitPlcOperation({ + operation: plcOp.data.operation, + }) + + await newAgent.com.atproto.server.activateAccount() + + const statusRes4 = await newAgent.com.atproto.server.checkAccountStatus() + expect(statusRes4.data).toMatchObject({ + activated: true, + validDid: true, + indexedRecords: 103, + privateStateValues: 0, + expectedBlobs: 3, + importedBlobs: 3, + }) + + await oldAgent.com.atproto.server.deactivateAccount({}) + + const statusResOldPds = + await oldAgent.com.atproto.server.checkAccountStatus() + expect(statusResOldPds.data).toMatchObject({ + activated: false, + validDid: false, + }) + + const postRes = await newAgent.api.app.bsky.feed.post.create( + { repo: alice }, + { + text: 'new pds!', + createdAt: new Date().toISOString(), + }, + ) + const postUri = new AtUri(postRes.uri) + const fetchedPost = await newAgent.api.app.bsky.feed.post.get({ + repo: postUri.hostname, + rkey: postUri.rkey, + }) + expect(fetchedPost.value.text).toEqual('new pds!') + const statusRes5 = await newAgent.com.atproto.server.checkAccountStatus() + expect(statusRes5.data.indexedRecords).toBe(104) + }) +}) diff --git a/packages/pds/tests/admin-auth.test.ts b/packages/pds/tests/admin-auth.test.ts index 4cf7d5c26a5..dffd9261874 100644 --- a/packages/pds/tests/admin-auth.test.ts +++ b/packages/pds/tests/admin-auth.test.ts @@ -96,7 +96,7 @@ describe('admin auth', () => { encoding: 'application/json', }, ) - await expect(attempt).rejects.toThrow('Untrusted issuer for admin actions') + await expect(attempt).rejects.toThrow('Untrusted issuer') }) it('does not allow requests with a bad signature', async () => { diff --git a/packages/pds/tests/entryway.test.ts b/packages/pds/tests/entryway.test.ts index d27c49c585c..8ef77239a4d 100644 --- a/packages/pds/tests/entryway.test.ts +++ b/packages/pds/tests/entryway.test.ts @@ -145,13 +145,11 @@ describe('entryway', () => { pds: pds.ctx.cfg.service.publicUrl, signer: rotationKey, }) - const tryCreateAccount = pdsAgent.api.com.atproto.server.createAccount( - { did: plcCreate.did, plcOp: plcCreate.op, handle: 'weirdalice.test' }, - { - headers: SeedClient.getHeaders(accessToken), - encoding: 'application/json', - }, - ) + const tryCreateAccount = pdsAgent.api.com.atproto.server.createAccount({ + did: plcCreate.did, + plcOp: plcCreate.op, + handle: 'weirdalice.test', + }) await expect(tryCreateAccount).rejects.toThrow('invalid plc operation') }) }) diff --git a/packages/pds/tests/moderation.test.ts b/packages/pds/tests/moderation.test.ts index ba58bcc70eb..4d3acfe2e25 100644 --- a/packages/pds/tests/moderation.test.ts +++ b/packages/pds/tests/moderation.test.ts @@ -198,14 +198,19 @@ describe('moderation', () => { }) it('prevents blob from being referenced again.', async () => { - const uploaded = await sc.uploadFile( + const referenceBlob = sc.post(sc.dids.carol, 'pic', [], [blobRef]) + await expect(referenceBlob).rejects.toThrow('Could not find blob:') + }) + + it('prevents blob from being reuploaded', async () => { + const attempt = sc.uploadFile( sc.dids.carol, '../dev-env/src/seed/img/key-alt.jpg', 'image/jpeg', ) - expect(uploaded.image.ref.equals(blobRef.image.ref)).toBeTruthy() - const referenceBlob = sc.post(sc.dids.carol, 'pic', [], [blobRef]) - await expect(referenceBlob).rejects.toThrow('Could not find blob:') + await expect(attempt).rejects.toThrow( + 'Blob has been takendown, cannot re-upload', + ) }) it('prevents image blob from being served.', async () => { diff --git a/packages/pds/tests/plc-operations.test.ts b/packages/pds/tests/plc-operations.test.ts new file mode 100644 index 00000000000..9c4ed9002cb --- /dev/null +++ b/packages/pds/tests/plc-operations.test.ts @@ -0,0 +1,226 @@ +import AtpAgent from '@atproto/api' +import { Secp256k1Keypair } from '@atproto/crypto' +import { SeedClient, TestNetworkNoAppView, basicSeed } from '@atproto/dev-env' +import * as plc from '@did-plc/lib' +import assert from 'assert' +import { once } from 'events' +import Mail from 'nodemailer/lib/mailer' +import { EventEmitter } from 'stream' +import { AppContext } from '../src' +import { check } from '@atproto/common' + +describe('plc operations', () => { + let network: TestNetworkNoAppView + let ctx: AppContext + let agent: AtpAgent + let sc: SeedClient + + const mailCatcher = new EventEmitter() + let _origSendMail + + let alice: string + + let sampleKey: string + + beforeAll(async () => { + network = await TestNetworkNoAppView.create({ + dbPostgresSchema: 'plc_operations', + }) + ctx = network.pds.ctx + const mailer = ctx.mailer + + sc = network.getSeedClient() + agent = network.pds.getClient() + + await basicSeed(sc) + alice = sc.dids.alice + await network.processAll() + + sampleKey = (await Secp256k1Keypair.create()).did() + + // Catch emails for use in tests + _origSendMail = mailer.transporter.sendMail + mailer.transporter.sendMail = async (opts) => { + const result = await _origSendMail.call(mailer.transporter, opts) + mailCatcher.emit('mail', opts) + return result + } + }) + + afterAll(async () => { + await network.close() + }) + + const getMailFrom = async (promise): Promise => { + const result = await Promise.all([once(mailCatcher, 'mail'), promise]) + return result[0][0] + } + + const getTokenFromMail = (mail: Mail.Options) => + mail.html?.toString().match(/>([a-z0-9]{5}-[a-z0-9]{5})) => { + const lastOp = await ctx.plcClient.getLastOp(did) + if (check.is(lastOp, plc.def.tombstone)) { + throw new Error('Did is tombstoned') + } + return plc.createUpdateOp(lastOp, ctx.plcRotationKey, (lastOp) => ({ + ...lastOp, + rotationKeys: op.rotationKeys ?? lastOp.rotationKeys, + alsoKnownAs: op.alsoKnownAs ?? lastOp.alsoKnownAs, + verificationMethods: op.verificationMethods ?? lastOp.verificationMethods, + services: op.services ?? lastOp.services, + })) + } + + const expectFailedOp = async ( + did: string, + op: Partial, + expectedErr?: string, + ) => { + const operation = await signOp(did, op) + const attempt = agent.com.atproto.identity.submitPlcOperation( + { operation }, + { + encoding: 'application/json', + headers: sc.getHeaders(alice), + }, + ) + await expect(attempt).rejects.toThrow(expectedErr) + } + + it("prevents submitting an operation that removes the server's rotation key", async () => { + await expectFailedOp( + alice, + { rotationKeys: [sampleKey] }, + "Rotation keys do not include server's rotation key", + ) + }) + + it('prevents submitting an operation that incorrectly sets the signing key', async () => { + await expectFailedOp( + alice, + { + verificationMethods: { + atproto: sampleKey, + }, + }, + 'Incorrect signing key', + ) + }) + + it('prevents submitting an operation that incorrectly sets the handle', async () => { + await expectFailedOp( + alice, + { + alsoKnownAs: ['at://new-alice.test'], + }, + 'Incorrect handle in alsoKnownAs', + ) + }) + + it('prevents submitting an operation that incorrectly sets the pds endpoint', async () => { + await expectFailedOp( + alice, + { + services: { + atproto_pds: { + type: 'AtprotoPersonalDataServer', + endpoint: 'https://example.com', + }, + }, + }, + 'Incorrect endpoint on atproto_pds service', + ) + }) + + it('prevents submitting an operation that incorrectly sets the pds service type', async () => { + await expectFailedOp( + alice, + { + services: { + atproto_pds: { + type: 'NotAPersonalDataServer', + endpoint: ctx.cfg.service.publicUrl, + }, + }, + }, + 'Incorrect type on atproto_pds service', + ) + }) + + it('does not allow signing plc operation without a token', async () => { + const attempt = agent.com.atproto.identity.signPlcOperation( + { + rotationKeys: [sampleKey], + }, + { encoding: 'application/json', headers: sc.getHeaders(alice) }, + ) + await expect(attempt).rejects.toThrow( + 'email confirmation token required to sign PLC operations', + ) + }) + + let token: string + + it('requests a plc signature', async () => { + const mail = await getMailFrom( + agent.api.com.atproto.identity.requestPlcOperationSignature(undefined, { + headers: sc.getHeaders(alice), + }), + ) + + expect(mail.to).toEqual(sc.accounts[alice].email) + expect(mail.html).toContain('PLC Update Requested') + + const gotToken = getTokenFromMail(mail) + assert(gotToken) + token = gotToken + }) + + it('does not sign a plc operation with a bad token', async () => { + const attempt = agent.api.com.atproto.identity.signPlcOperation( + { + token: '123456', + rotationKeys: [sampleKey], + }, + { encoding: 'application/json', headers: sc.getHeaders(alice) }, + ) + await expect(attempt).rejects.toThrow('Token is invalid') + }) + + let operation: any + + it('signs a plc operation with a valid token', async () => { + const res = await agent.api.com.atproto.identity.signPlcOperation( + { + token, + rotationKeys: [sampleKey, ctx.plcRotationKey.did()], + }, + { encoding: 'application/json', headers: sc.getHeaders(alice) }, + ) + const currData = await ctx.plcClient.getDocumentData(alice) + expect(res.data.operation['alsoKnownAs']).toEqual(currData.alsoKnownAs) + expect(res.data.operation['verificationMethods']).toEqual( + currData.verificationMethods, + ) + expect(res.data.operation['services']).toEqual(currData.services) + expect(res.data.operation['rotationKeys']).toEqual([ + sampleKey, + ctx.plcRotationKey.did(), + ]) + operation = res.data.operation + }) + + it('submits a valid operation', async () => { + await agent.com.atproto.identity.submitPlcOperation( + { operation }, + { + encoding: 'application/json', + headers: sc.getHeaders(alice), + }, + ) + const didData = await ctx.plcClient.getDocumentData(alice) + expect(didData.rotationKeys).toEqual([sampleKey, ctx.plcRotationKey.did()]) + }) +}) diff --git a/packages/pds/tests/transfer-repo.test.ts b/packages/pds/tests/transfer-repo.test.ts deleted file mode 100644 index 48309b821ae..00000000000 --- a/packages/pds/tests/transfer-repo.test.ts +++ /dev/null @@ -1,221 +0,0 @@ -import path from 'node:path' -import os from 'node:os' -import axios from 'axios' -import * as ui8 from 'uint8arrays' -import { SeedClient, TestPds, TestPlc, mockResolvers } from '@atproto/dev-env' -import AtpAgent from '@atproto/api' -import * as pdsEntryway from '@atproto/pds-entryway' -import { Secp256k1Keypair, randomStr } from '@atproto/crypto' -import * as plcLib from '@did-plc/lib' -import getPort from 'get-port' - -describe('transfer repo', () => { - let plc: TestPlc - let pds: TestPds - let entryway: pdsEntryway.PDS - let entrywaySc: SeedClient - let pdsAgent: AtpAgent - let entrywayAgent: AtpAgent - - let did: string - const accountDetail = { - email: 'alice@test.com', - handle: 'alice.test', - password: 'test123', - } - - beforeAll(async () => { - const jwtSigningKey = await Secp256k1Keypair.create({ exportable: true }) - const plcRotationKey = await Secp256k1Keypair.create({ exportable: true }) - const entrywayPort = await getPort() - plc = await TestPlc.create({}) - pds = await TestPds.create({ - entrywayUrl: `http://localhost:${entrywayPort}`, - entrywayDid: 'did:example:entryway', - entrywayJwtVerifyKeyK256PublicKeyHex: getPublicHex(jwtSigningKey), - entrywayPlcRotationKey: plcRotationKey.did(), - adminPassword: 'admin-pass', - serviceHandleDomains: [], - didPlcUrl: plc.url, - serviceDid: 'did:example:pds', - inviteRequired: false, - }) - entryway = await createEntryway({ - dbPostgresSchema: 'transfer_repo', - port: entrywayPort, - adminPassword: 'admin-pass', - jwtSigningKeyK256PrivateKeyHex: await getPrivateHex(jwtSigningKey), - plcRotationKeyK256PrivateKeyHex: await getPrivateHex(plcRotationKey), - inviteRequired: false, - serviceDid: 'did:example:entryway', - didPlcUrl: plc.url, - }) - mockResolvers(pds.ctx.idResolver, pds) - mockResolvers(entryway.ctx.idResolver, pds) - await entryway.ctx.db.db - .insertInto('pds') - .values({ - did: pds.ctx.cfg.service.did, - host: new URL(pds.ctx.cfg.service.publicUrl).host, - weight: 0, - }) - .execute() - pdsAgent = pds.getClient() - entrywayAgent = new AtpAgent({ - service: entryway.ctx.cfg.service.publicUrl, - }) - - // @ts-ignore network not needed - entrywaySc = new SeedClient({}, entrywayAgent) - await entrywaySc.createAccount('alice', accountDetail) - did = entrywaySc.dids.alice - for (let i = 0; i < 50; i++) { - const post = await entrywaySc.post(did, 'blah') - await entrywaySc.like(did, post.ref) - } - const img = await entrywaySc.uploadFile( - did, - '../dev-env/src/seed/img/key-landscape-small.jpg', - 'image/jpeg', - ) - await entrywaySc.post(did, 'img post', undefined, [img]) - }) - - afterAll(async () => { - await plc.close() - await entryway.destroy() - await pds.close() - }) - - it('transfers', async () => { - const signingKeyRes = - await pdsAgent.api.com.atproto.server.reserveSigningKey({ did }) - const signingKey = signingKeyRes.data.signingKey - - const repo = await entrywayAgent.api.com.atproto.sync.getRepo({ did }) - const importRes = await axios.post( - `${pds.url}/xrpc/com.atproto.temp.importRepo`, - repo.data, - { - params: { did }, - headers: { - 'content-type': 'application/vnd.ipld.car', - ...pds.adminAuthHeaders('admin'), - }, - decompress: true, - responseType: 'stream', - }, - ) - - for await (const _log of importRes.data) { - // noop just wait till import is finished - } - - const lastOp = await pds.ctx.plcClient.getLastOp(did) - if (!lastOp || lastOp.type === 'plc_tombstone') { - throw new Error('could not find last plc op') - } - const plcOp = await plcLib.createUpdateOp( - lastOp, - entryway.ctx.plcRotationKey, - (normalized) => ({ - ...normalized, - rotationKeys: [pds.ctx.plcRotationKey.did()], - verificationMethods: { - atproto: signingKey, - }, - services: { - atproto_pds: { - type: 'AtprotoPersonalDataServer', - endpoint: pds.ctx.cfg.service.publicUrl, - }, - }, - }), - ) - await pdsAgent.api.com.atproto.temp.transferAccount( - { - did, - handle: accountDetail.handle, - plcOp, - }, - { headers: pds.adminAuthHeaders('admin'), encoding: 'application/json' }, - ) - - await entryway.ctx.db.db - .updateTable('user_account') - .set({ - pdsId: entryway.ctx.db.db.selectFrom('pds').select('id').limit(1), - }) - .where('did', '=', did) - .execute() - - await pdsAgent.login({ - identifier: accountDetail.handle, - password: accountDetail.password, - }) - - await pdsAgent.api.app.bsky.feed.post.create( - { repo: did }, - { - text: 'asdflsidkfu', - createdAt: new Date().toISOString(), - }, - ) - - const listPosts = await pdsAgent.api.com.atproto.repo.listRecords({ - repo: did, - collection: 'app.bsky.feed.post', - limit: 100, - }) - const listLikes = await pdsAgent.api.com.atproto.repo.listRecords({ - repo: did, - collection: 'app.bsky.feed.like', - limit: 100, - }) - - expect(listPosts.data.records.length).toBe(52) - expect(listLikes.data.records.length).toBe(50) - }) -}) - -const createEntryway = async ( - config: pdsEntryway.ServerEnvironment & { - adminPassword: string - jwtSigningKeyK256PrivateKeyHex: string - plcRotationKeyK256PrivateKeyHex: string - }, -) => { - const signingKey = await Secp256k1Keypair.create({ exportable: true }) - const recoveryKey = await Secp256k1Keypair.create({ exportable: true }) - const env: pdsEntryway.ServerEnvironment = { - isEntryway: true, - recoveryDidKey: recoveryKey.did(), - serviceHandleDomains: ['.test'], - dbPostgresUrl: process.env.DB_POSTGRES_URL, - blobstoreDiskLocation: path.join(os.tmpdir(), randomStr(8, 'base32')), - bskyAppViewUrl: 'https://appview.invalid', - bskyAppViewDid: 'did:example:invalid', - bskyAppViewCdnUrlPattern: 'http://cdn.appview.com/%s/%s/%s', - jwtSecret: randomStr(8, 'base32'), - repoSigningKeyK256PrivateKeyHex: await getPrivateHex(signingKey), - modServiceUrl: 'https://mod.invalid', - modServiceDid: 'did:example:invalid', - ...config, - } - const cfg = pdsEntryway.envToCfg(env) - const secrets = pdsEntryway.envToSecrets(env) - const server = await pdsEntryway.PDS.create(cfg, secrets) - await server.ctx.db.migrateToLatestOrThrow() - await server.start() - // @TODO temp hack because entryway teardown calls signupActivator.run() by mistake - server.ctx.signupActivator.run = server.ctx.signupActivator.destroy - return server -} - -const getPublicHex = (key: Secp256k1Keypair) => { - return key.publicKeyStr('hex') -} - -const getPrivateHex = async (key: Secp256k1Keypair) => { - return ui8.toString(await key.export(), 'hex') -} From 1a12c7e34b029e114cacd1c84949ea82613a969b Mon Sep 17 00:00:00 2001 From: Daniel Holmgren Date: Wed, 21 Feb 2024 15:10:12 -0600 Subject: [PATCH 019/103] Firehose Identity event (#2208) * add new identity event * add note for tombstone * send identity evts * add emission of identity evts * emit handle event on activate account & fix subscribeRepos * add time to evt * update indexer for tests * rm logs --- lexicons/com/atproto/sync/subscribeRepos.json | 25 ++++++++++++--- packages/api/src/client/lexicons.ts | 30 +++++++++++++++--- .../src/client/types/app/bsky/feed/post.ts | 2 +- .../types/com/atproto/sync/subscribeRepos.ts | 26 ++++++++++++++-- packages/bsky/src/indexer/subscription.ts | 6 ++++ packages/bsky/src/ingester/subscription.ts | 2 ++ packages/bsky/src/lexicon/lexicons.ts | 30 +++++++++++++++--- .../src/lexicon/types/app/bsky/feed/post.ts | 2 +- .../types/com/atproto/sync/subscribeRepos.ts | 27 ++++++++++++++-- packages/ozone/src/lexicon/lexicons.ts | 30 +++++++++++++++--- .../src/lexicon/types/app/bsky/feed/post.ts | 2 +- .../types/com/atproto/sync/subscribeRepos.ts | 27 ++++++++++++++-- .../api/com/atproto/server/activateAccount.ts | 17 +++++++++- .../api/com/atproto/server/createAccount.ts | 1 + .../api/com/atproto/server/deleteAccount.ts | 1 + .../api/com/atproto/sync/subscribeRepos.ts | 7 +++++ packages/pds/src/lexicon/lexicons.ts | 30 +++++++++++++++--- .../src/lexicon/types/app/bsky/feed/post.ts | 2 +- .../types/com/atproto/sync/subscribeRepos.ts | 27 ++++++++++++++-- packages/pds/src/sequencer/db/schema.ts | 1 + packages/pds/src/sequencer/events.ts | 31 ++++++++++++++++++- packages/pds/src/sequencer/sequencer.ts | 14 +++++++++ packages/pds/tests/sequencer.test.ts | 7 +++-- 23 files changed, 306 insertions(+), 41 deletions(-) diff --git a/lexicons/com/atproto/sync/subscribeRepos.json b/lexicons/com/atproto/sync/subscribeRepos.json index 73a34a3b49e..31d68b91c07 100644 --- a/lexicons/com/atproto/sync/subscribeRepos.json +++ b/lexicons/com/atproto/sync/subscribeRepos.json @@ -17,7 +17,14 @@ "message": { "schema": { "type": "union", - "refs": ["#commit", "#handle", "#migrate", "#tombstone", "#info"] + "refs": [ + "#commit", + "#identity", + "#handle", + "#migrate", + "#tombstone", + "#info" + ] } }, "errors": [ @@ -104,9 +111,19 @@ } } }, + "identity": { + "type": "object", + "description": "Represents a change to an account's identity. Could be an updated handle, signing key, or pds hosting endpoint. Serves as a prod to all downstream services to refresh their identity cache.", + "required": ["seq", "did", "time"], + "properties": { + "seq": { "type": "integer" }, + "did": { "type": "string", "format": "did" }, + "time": { "type": "string", "format": "datetime" } + } + }, "handle": { "type": "object", - "description": "Represents an update of the account's handle, or transition to/from invalid state.", + "description": "Represents an update of the account's handle, or transition to/from invalid state. NOTE: Will be deprecated in favor of #identity.", "required": ["seq", "did", "handle", "time"], "properties": { "seq": { "type": "integer" }, @@ -117,7 +134,7 @@ }, "migrate": { "type": "object", - "description": "Represents an account moving from one PDS instance to another. NOTE: not implemented; full account migration may introduce a new message instead.", + "description": "Represents an account moving from one PDS instance to another. NOTE: not implemented; account migration uses #identity instead", "required": ["seq", "did", "migrateTo", "time"], "nullable": ["migrateTo"], "properties": { @@ -129,7 +146,7 @@ }, "tombstone": { "type": "object", - "description": "Indicates that an account has been deleted.", + "description": "Indicates that an account has been deleted. NOTE: may be deprecated in favor of #identity or a future #account event", "required": ["seq", "did", "time"], "properties": { "seq": { "type": "integer" }, diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index 6164b1706a3..d873a876865 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -4579,6 +4579,7 @@ export const schemaDict = { type: 'union', refs: [ 'lex:com.atproto.sync.subscribeRepos#commit', + 'lex:com.atproto.sync.subscribeRepos#identity', 'lex:com.atproto.sync.subscribeRepos#handle', 'lex:com.atproto.sync.subscribeRepos#migrate', 'lex:com.atproto.sync.subscribeRepos#tombstone', @@ -4685,10 +4686,29 @@ export const schemaDict = { }, }, }, + identity: { + type: 'object', + description: + "Represents a change to an account's identity. Could be an updated handle, signing key, or pds hosting endpoint. Serves as a prod to all downstream services to refresh their identity cache.", + required: ['seq', 'did', 'time'], + properties: { + seq: { + type: 'integer', + }, + did: { + type: 'string', + format: 'did', + }, + time: { + type: 'string', + format: 'datetime', + }, + }, + }, handle: { type: 'object', description: - "Represents an update of the account's handle, or transition to/from invalid state.", + "Represents an update of the account's handle, or transition to/from invalid state. NOTE: Will be deprecated in favor of #identity.", required: ['seq', 'did', 'handle', 'time'], properties: { seq: { @@ -4711,7 +4731,7 @@ export const schemaDict = { migrate: { type: 'object', description: - 'Represents an account moving from one PDS instance to another. NOTE: not implemented; full account migration may introduce a new message instead.', + 'Represents an account moving from one PDS instance to another. NOTE: not implemented; account migration uses #identity instead', required: ['seq', 'did', 'migrateTo', 'time'], nullable: ['migrateTo'], properties: { @@ -4733,7 +4753,8 @@ export const schemaDict = { }, tombstone: { type: 'object', - description: 'Indicates that an account has been deleted.', + description: + 'Indicates that an account has been deleted. NOTE: may be deprecated in favor of #identity or a future #account event', required: ['seq', 'did', 'time'], properties: { seq: { @@ -7087,7 +7108,8 @@ export const schemaDict = { }, tags: { type: 'array', - description: 'Additional non-inline tags describing this post.', + description: + 'Additional hashtags, in addition to any included in post text and facets.', maxLength: 8, items: { type: 'string', diff --git a/packages/api/src/client/types/app/bsky/feed/post.ts b/packages/api/src/client/types/app/bsky/feed/post.ts index 401510f9ef9..0de5192af77 100644 --- a/packages/api/src/client/types/app/bsky/feed/post.ts +++ b/packages/api/src/client/types/app/bsky/feed/post.ts @@ -32,7 +32,7 @@ export interface Record { labels?: | ComAtprotoLabelDefs.SelfLabels | { $type: string; [k: string]: unknown } - /** Additional non-inline tags describing this post. */ + /** Additional hashtags, in addition to any included in post text and facets. */ tags?: string[] /** Client-declared timestamp when this post was originally created. */ createdAt: string diff --git a/packages/api/src/client/types/com/atproto/sync/subscribeRepos.ts b/packages/api/src/client/types/com/atproto/sync/subscribeRepos.ts index 708fc80c85b..f4a362f755f 100644 --- a/packages/api/src/client/types/com/atproto/sync/subscribeRepos.ts +++ b/packages/api/src/client/types/com/atproto/sync/subscribeRepos.ts @@ -46,7 +46,27 @@ export function validateCommit(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#commit', v) } -/** Represents an update of the account's handle, or transition to/from invalid state. */ +/** Represents a change to an account's identity. Could be an updated handle, signing key, or pds hosting endpoint. Serves as a prod to all downstream services to refresh their identity cache. */ +export interface Identity { + seq: number + did: string + time: string + [k: string]: unknown +} + +export function isIdentity(v: unknown): v is Identity { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'com.atproto.sync.subscribeRepos#identity' + ) +} + +export function validateIdentity(v: unknown): ValidationResult { + return lexicons.validate('com.atproto.sync.subscribeRepos#identity', v) +} + +/** Represents an update of the account's handle, or transition to/from invalid state. NOTE: Will be deprecated in favor of #identity. */ export interface Handle { seq: number did: string @@ -67,7 +87,7 @@ export function validateHandle(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#handle', v) } -/** Represents an account moving from one PDS instance to another. NOTE: not implemented; full account migration may introduce a new message instead. */ +/** Represents an account moving from one PDS instance to another. NOTE: not implemented; account migration uses #identity instead */ export interface Migrate { seq: number did: string @@ -88,7 +108,7 @@ export function validateMigrate(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#migrate', v) } -/** Indicates that an account has been deleted. */ +/** Indicates that an account has been deleted. NOTE: may be deprecated in favor of #identity or a future #account event */ export interface Tombstone { seq: number did: string diff --git a/packages/bsky/src/indexer/subscription.ts b/packages/bsky/src/indexer/subscription.ts index abc672db3b0..907da954f49 100644 --- a/packages/bsky/src/indexer/subscription.ts +++ b/packages/bsky/src/indexer/subscription.ts @@ -145,6 +145,8 @@ export class IndexerSubscription { await this.handleCommit(msg) } else if (message.isHandle(msg)) { await this.handleUpdateHandle(msg) + } else if (message.isIdentity(msg)) { + await this.handleIdentityEvt(msg) } else if (message.isTombstone(msg)) { await this.handleTombstone(msg) } else if (message.isMigrate(msg)) { @@ -244,6 +246,10 @@ export class IndexerSubscription { await this.indexingSvc.indexHandle(msg.did, msg.time, true) } + private async handleIdentityEvt(msg: message.Identity) { + await this.indexingSvc.indexHandle(msg.did, msg.time, true) + } + private async handleTombstone(msg: message.Tombstone) { await this.indexingSvc.tombstoneActor(msg.did) } diff --git a/packages/bsky/src/ingester/subscription.ts b/packages/bsky/src/ingester/subscription.ts index 14f301e07f9..6db05ba1786 100644 --- a/packages/bsky/src/ingester/subscription.ts +++ b/packages/bsky/src/ingester/subscription.ts @@ -156,6 +156,8 @@ function getMessageDetails(msg: Message): return { seq: msg.seq, repo: msg.repo, message: msg } } else if (message.isHandle(msg)) { return { seq: msg.seq, repo: msg.did, message: msg } + } else if (message.isIdentity(msg)) { + return { seq: msg.seq, repo: msg.did, message: msg } } else if (message.isMigrate(msg)) { return { seq: msg.seq, repo: msg.did, message: msg } } else if (message.isTombstone(msg)) { diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index 6164b1706a3..d873a876865 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -4579,6 +4579,7 @@ export const schemaDict = { type: 'union', refs: [ 'lex:com.atproto.sync.subscribeRepos#commit', + 'lex:com.atproto.sync.subscribeRepos#identity', 'lex:com.atproto.sync.subscribeRepos#handle', 'lex:com.atproto.sync.subscribeRepos#migrate', 'lex:com.atproto.sync.subscribeRepos#tombstone', @@ -4685,10 +4686,29 @@ export const schemaDict = { }, }, }, + identity: { + type: 'object', + description: + "Represents a change to an account's identity. Could be an updated handle, signing key, or pds hosting endpoint. Serves as a prod to all downstream services to refresh their identity cache.", + required: ['seq', 'did', 'time'], + properties: { + seq: { + type: 'integer', + }, + did: { + type: 'string', + format: 'did', + }, + time: { + type: 'string', + format: 'datetime', + }, + }, + }, handle: { type: 'object', description: - "Represents an update of the account's handle, or transition to/from invalid state.", + "Represents an update of the account's handle, or transition to/from invalid state. NOTE: Will be deprecated in favor of #identity.", required: ['seq', 'did', 'handle', 'time'], properties: { seq: { @@ -4711,7 +4731,7 @@ export const schemaDict = { migrate: { type: 'object', description: - 'Represents an account moving from one PDS instance to another. NOTE: not implemented; full account migration may introduce a new message instead.', + 'Represents an account moving from one PDS instance to another. NOTE: not implemented; account migration uses #identity instead', required: ['seq', 'did', 'migrateTo', 'time'], nullable: ['migrateTo'], properties: { @@ -4733,7 +4753,8 @@ export const schemaDict = { }, tombstone: { type: 'object', - description: 'Indicates that an account has been deleted.', + description: + 'Indicates that an account has been deleted. NOTE: may be deprecated in favor of #identity or a future #account event', required: ['seq', 'did', 'time'], properties: { seq: { @@ -7087,7 +7108,8 @@ export const schemaDict = { }, tags: { type: 'array', - description: 'Additional non-inline tags describing this post.', + description: + 'Additional hashtags, in addition to any included in post text and facets.', maxLength: 8, items: { type: 'string', diff --git a/packages/bsky/src/lexicon/types/app/bsky/feed/post.ts b/packages/bsky/src/lexicon/types/app/bsky/feed/post.ts index c30825e118a..881e3d199aa 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/feed/post.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/feed/post.ts @@ -32,7 +32,7 @@ export interface Record { labels?: | ComAtprotoLabelDefs.SelfLabels | { $type: string; [k: string]: unknown } - /** Additional non-inline tags describing this post. */ + /** Additional hashtags, in addition to any included in post text and facets. */ tags?: string[] /** Client-declared timestamp when this post was originally created. */ createdAt: string diff --git a/packages/bsky/src/lexicon/types/com/atproto/sync/subscribeRepos.ts b/packages/bsky/src/lexicon/types/com/atproto/sync/subscribeRepos.ts index 689ea76daee..19874b06083 100644 --- a/packages/bsky/src/lexicon/types/com/atproto/sync/subscribeRepos.ts +++ b/packages/bsky/src/lexicon/types/com/atproto/sync/subscribeRepos.ts @@ -15,6 +15,7 @@ export interface QueryParams { export type OutputSchema = | Commit + | Identity | Handle | Migrate | Tombstone @@ -71,7 +72,27 @@ export function validateCommit(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#commit', v) } -/** Represents an update of the account's handle, or transition to/from invalid state. */ +/** Represents a change to an account's identity. Could be an updated handle, signing key, or pds hosting endpoint. Serves as a prod to all downstream services to refresh their identity cache. */ +export interface Identity { + seq: number + did: string + time: string + [k: string]: unknown +} + +export function isIdentity(v: unknown): v is Identity { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'com.atproto.sync.subscribeRepos#identity' + ) +} + +export function validateIdentity(v: unknown): ValidationResult { + return lexicons.validate('com.atproto.sync.subscribeRepos#identity', v) +} + +/** Represents an update of the account's handle, or transition to/from invalid state. NOTE: Will be deprecated in favor of #identity. */ export interface Handle { seq: number did: string @@ -92,7 +113,7 @@ export function validateHandle(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#handle', v) } -/** Represents an account moving from one PDS instance to another. NOTE: not implemented; full account migration may introduce a new message instead. */ +/** Represents an account moving from one PDS instance to another. NOTE: not implemented; account migration uses #identity instead */ export interface Migrate { seq: number did: string @@ -113,7 +134,7 @@ export function validateMigrate(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#migrate', v) } -/** Indicates that an account has been deleted. */ +/** Indicates that an account has been deleted. NOTE: may be deprecated in favor of #identity or a future #account event */ export interface Tombstone { seq: number did: string diff --git a/packages/ozone/src/lexicon/lexicons.ts b/packages/ozone/src/lexicon/lexicons.ts index 6164b1706a3..d873a876865 100644 --- a/packages/ozone/src/lexicon/lexicons.ts +++ b/packages/ozone/src/lexicon/lexicons.ts @@ -4579,6 +4579,7 @@ export const schemaDict = { type: 'union', refs: [ 'lex:com.atproto.sync.subscribeRepos#commit', + 'lex:com.atproto.sync.subscribeRepos#identity', 'lex:com.atproto.sync.subscribeRepos#handle', 'lex:com.atproto.sync.subscribeRepos#migrate', 'lex:com.atproto.sync.subscribeRepos#tombstone', @@ -4685,10 +4686,29 @@ export const schemaDict = { }, }, }, + identity: { + type: 'object', + description: + "Represents a change to an account's identity. Could be an updated handle, signing key, or pds hosting endpoint. Serves as a prod to all downstream services to refresh their identity cache.", + required: ['seq', 'did', 'time'], + properties: { + seq: { + type: 'integer', + }, + did: { + type: 'string', + format: 'did', + }, + time: { + type: 'string', + format: 'datetime', + }, + }, + }, handle: { type: 'object', description: - "Represents an update of the account's handle, or transition to/from invalid state.", + "Represents an update of the account's handle, or transition to/from invalid state. NOTE: Will be deprecated in favor of #identity.", required: ['seq', 'did', 'handle', 'time'], properties: { seq: { @@ -4711,7 +4731,7 @@ export const schemaDict = { migrate: { type: 'object', description: - 'Represents an account moving from one PDS instance to another. NOTE: not implemented; full account migration may introduce a new message instead.', + 'Represents an account moving from one PDS instance to another. NOTE: not implemented; account migration uses #identity instead', required: ['seq', 'did', 'migrateTo', 'time'], nullable: ['migrateTo'], properties: { @@ -4733,7 +4753,8 @@ export const schemaDict = { }, tombstone: { type: 'object', - description: 'Indicates that an account has been deleted.', + description: + 'Indicates that an account has been deleted. NOTE: may be deprecated in favor of #identity or a future #account event', required: ['seq', 'did', 'time'], properties: { seq: { @@ -7087,7 +7108,8 @@ export const schemaDict = { }, tags: { type: 'array', - description: 'Additional non-inline tags describing this post.', + description: + 'Additional hashtags, in addition to any included in post text and facets.', maxLength: 8, items: { type: 'string', diff --git a/packages/ozone/src/lexicon/types/app/bsky/feed/post.ts b/packages/ozone/src/lexicon/types/app/bsky/feed/post.ts index c30825e118a..881e3d199aa 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/feed/post.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/feed/post.ts @@ -32,7 +32,7 @@ export interface Record { labels?: | ComAtprotoLabelDefs.SelfLabels | { $type: string; [k: string]: unknown } - /** Additional non-inline tags describing this post. */ + /** Additional hashtags, in addition to any included in post text and facets. */ tags?: string[] /** Client-declared timestamp when this post was originally created. */ createdAt: string diff --git a/packages/ozone/src/lexicon/types/com/atproto/sync/subscribeRepos.ts b/packages/ozone/src/lexicon/types/com/atproto/sync/subscribeRepos.ts index 689ea76daee..19874b06083 100644 --- a/packages/ozone/src/lexicon/types/com/atproto/sync/subscribeRepos.ts +++ b/packages/ozone/src/lexicon/types/com/atproto/sync/subscribeRepos.ts @@ -15,6 +15,7 @@ export interface QueryParams { export type OutputSchema = | Commit + | Identity | Handle | Migrate | Tombstone @@ -71,7 +72,27 @@ export function validateCommit(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#commit', v) } -/** Represents an update of the account's handle, or transition to/from invalid state. */ +/** Represents a change to an account's identity. Could be an updated handle, signing key, or pds hosting endpoint. Serves as a prod to all downstream services to refresh their identity cache. */ +export interface Identity { + seq: number + did: string + time: string + [k: string]: unknown +} + +export function isIdentity(v: unknown): v is Identity { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'com.atproto.sync.subscribeRepos#identity' + ) +} + +export function validateIdentity(v: unknown): ValidationResult { + return lexicons.validate('com.atproto.sync.subscribeRepos#identity', v) +} + +/** Represents an update of the account's handle, or transition to/from invalid state. NOTE: Will be deprecated in favor of #identity. */ export interface Handle { seq: number did: string @@ -92,7 +113,7 @@ export function validateHandle(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#handle', v) } -/** Represents an account moving from one PDS instance to another. NOTE: not implemented; full account migration may introduce a new message instead. */ +/** Represents an account moving from one PDS instance to another. NOTE: not implemented; account migration uses #identity instead */ export interface Migrate { seq: number did: string @@ -113,7 +134,7 @@ export function validateMigrate(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#migrate', v) } -/** Indicates that an account has been deleted. */ +/** Indicates that an account has been deleted. NOTE: may be deprecated in favor of #identity or a future #account event */ export interface Tombstone { seq: number did: string diff --git a/packages/pds/src/api/com/atproto/server/activateAccount.ts b/packages/pds/src/api/com/atproto/server/activateAccount.ts index 90170084801..c666fd865eb 100644 --- a/packages/pds/src/api/com/atproto/server/activateAccount.ts +++ b/packages/pds/src/api/com/atproto/server/activateAccount.ts @@ -1,7 +1,9 @@ +import { CidSet } from '@atproto/repo' +import { InvalidRequestError } from '@atproto/xrpc-server' +import { INVALID_HANDLE } from '@atproto/syntax' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { assertValidDidDocumentForService } from './util' -import { CidSet } from '@atproto/repo' export default function (server: Server, ctx: AppContext) { server.com.atproto.server.activateAccount({ @@ -11,6 +13,13 @@ export default function (server: Server, ctx: AppContext) { await assertValidDidDocumentForService(ctx, requester) + const account = await ctx.accountManager.getAccount(requester, { + includeDeactivated: true, + }) + if (!account) { + throw new InvalidRequestError('user not found', 'AccountNotFound') + } + await ctx.accountManager.activateAccount(requester) const commitData = await ctx.actorStore.read(requester, async (store) => { @@ -26,6 +35,12 @@ export default function (server: Server, ctx: AppContext) { } }) + // @NOTE: we're over-emitting for now for backwards compatibility, can reduce this in the future + await ctx.sequencer.sequenceIdentityEvt(requester) + await ctx.sequencer.sequenceHandleUpdate( + requester, + account.handle ?? INVALID_HANDLE, + ) await ctx.sequencer.sequenceCommit(requester, commitData, []) }, }) diff --git a/packages/pds/src/api/com/atproto/server/createAccount.ts b/packages/pds/src/api/com/atproto/server/createAccount.ts index 1a9a4ef4570..b6758b8c614 100644 --- a/packages/pds/src/api/com/atproto/server/createAccount.ts +++ b/packages/pds/src/api/com/atproto/server/createAccount.ts @@ -69,6 +69,7 @@ export default function (server: Server, ctx: AppContext) { if (!deactivated) { await ctx.sequencer.sequenceCommit(did, commit, []) + await ctx.sequencer.sequenceIdentityEvt(did) } await ctx.accountManager.updateRepoRoot(did, commit.cid, commit.rev) didDoc = await didDocForSession(ctx, did, true) diff --git a/packages/pds/src/api/com/atproto/server/deleteAccount.ts b/packages/pds/src/api/com/atproto/server/deleteAccount.ts index f4c3f59c170..c42810ede40 100644 --- a/packages/pds/src/api/com/atproto/server/deleteAccount.ts +++ b/packages/pds/src/api/com/atproto/server/deleteAccount.ts @@ -44,6 +44,7 @@ export default function (server: Server, ctx: AppContext) { ) await ctx.actorStore.destroy(did) await ctx.accountManager.deleteAccount(did) + await ctx.sequencer.sequenceIdentityEvt(did) await ctx.sequencer.sequenceTombstone(did) await ctx.sequencer.deleteAllForUser(did) }, diff --git a/packages/pds/src/api/com/atproto/sync/subscribeRepos.ts b/packages/pds/src/api/com/atproto/sync/subscribeRepos.ts index 8302760a75f..1b9c16354f0 100644 --- a/packages/pds/src/api/com/atproto/sync/subscribeRepos.ts +++ b/packages/pds/src/api/com/atproto/sync/subscribeRepos.ts @@ -52,6 +52,13 @@ export default function (server: Server, ctx: AppContext) { time: evt.time, ...evt.evt, } + } else if (evt.type === 'identity') { + yield { + $type: '#identity', + seq: evt.seq, + time: evt.time, + ...evt.evt, + } } else if (evt.type === 'tombstone') { yield { $type: '#tombstone', diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index 6164b1706a3..d873a876865 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -4579,6 +4579,7 @@ export const schemaDict = { type: 'union', refs: [ 'lex:com.atproto.sync.subscribeRepos#commit', + 'lex:com.atproto.sync.subscribeRepos#identity', 'lex:com.atproto.sync.subscribeRepos#handle', 'lex:com.atproto.sync.subscribeRepos#migrate', 'lex:com.atproto.sync.subscribeRepos#tombstone', @@ -4685,10 +4686,29 @@ export const schemaDict = { }, }, }, + identity: { + type: 'object', + description: + "Represents a change to an account's identity. Could be an updated handle, signing key, or pds hosting endpoint. Serves as a prod to all downstream services to refresh their identity cache.", + required: ['seq', 'did', 'time'], + properties: { + seq: { + type: 'integer', + }, + did: { + type: 'string', + format: 'did', + }, + time: { + type: 'string', + format: 'datetime', + }, + }, + }, handle: { type: 'object', description: - "Represents an update of the account's handle, or transition to/from invalid state.", + "Represents an update of the account's handle, or transition to/from invalid state. NOTE: Will be deprecated in favor of #identity.", required: ['seq', 'did', 'handle', 'time'], properties: { seq: { @@ -4711,7 +4731,7 @@ export const schemaDict = { migrate: { type: 'object', description: - 'Represents an account moving from one PDS instance to another. NOTE: not implemented; full account migration may introduce a new message instead.', + 'Represents an account moving from one PDS instance to another. NOTE: not implemented; account migration uses #identity instead', required: ['seq', 'did', 'migrateTo', 'time'], nullable: ['migrateTo'], properties: { @@ -4733,7 +4753,8 @@ export const schemaDict = { }, tombstone: { type: 'object', - description: 'Indicates that an account has been deleted.', + description: + 'Indicates that an account has been deleted. NOTE: may be deprecated in favor of #identity or a future #account event', required: ['seq', 'did', 'time'], properties: { seq: { @@ -7087,7 +7108,8 @@ export const schemaDict = { }, tags: { type: 'array', - description: 'Additional non-inline tags describing this post.', + description: + 'Additional hashtags, in addition to any included in post text and facets.', maxLength: 8, items: { type: 'string', diff --git a/packages/pds/src/lexicon/types/app/bsky/feed/post.ts b/packages/pds/src/lexicon/types/app/bsky/feed/post.ts index c30825e118a..881e3d199aa 100644 --- a/packages/pds/src/lexicon/types/app/bsky/feed/post.ts +++ b/packages/pds/src/lexicon/types/app/bsky/feed/post.ts @@ -32,7 +32,7 @@ export interface Record { labels?: | ComAtprotoLabelDefs.SelfLabels | { $type: string; [k: string]: unknown } - /** Additional non-inline tags describing this post. */ + /** Additional hashtags, in addition to any included in post text and facets. */ tags?: string[] /** Client-declared timestamp when this post was originally created. */ createdAt: string diff --git a/packages/pds/src/lexicon/types/com/atproto/sync/subscribeRepos.ts b/packages/pds/src/lexicon/types/com/atproto/sync/subscribeRepos.ts index 689ea76daee..19874b06083 100644 --- a/packages/pds/src/lexicon/types/com/atproto/sync/subscribeRepos.ts +++ b/packages/pds/src/lexicon/types/com/atproto/sync/subscribeRepos.ts @@ -15,6 +15,7 @@ export interface QueryParams { export type OutputSchema = | Commit + | Identity | Handle | Migrate | Tombstone @@ -71,7 +72,27 @@ export function validateCommit(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#commit', v) } -/** Represents an update of the account's handle, or transition to/from invalid state. */ +/** Represents a change to an account's identity. Could be an updated handle, signing key, or pds hosting endpoint. Serves as a prod to all downstream services to refresh their identity cache. */ +export interface Identity { + seq: number + did: string + time: string + [k: string]: unknown +} + +export function isIdentity(v: unknown): v is Identity { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'com.atproto.sync.subscribeRepos#identity' + ) +} + +export function validateIdentity(v: unknown): ValidationResult { + return lexicons.validate('com.atproto.sync.subscribeRepos#identity', v) +} + +/** Represents an update of the account's handle, or transition to/from invalid state. NOTE: Will be deprecated in favor of #identity. */ export interface Handle { seq: number did: string @@ -92,7 +113,7 @@ export function validateHandle(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#handle', v) } -/** Represents an account moving from one PDS instance to another. NOTE: not implemented; full account migration may introduce a new message instead. */ +/** Represents an account moving from one PDS instance to another. NOTE: not implemented; account migration uses #identity instead */ export interface Migrate { seq: number did: string @@ -113,7 +134,7 @@ export function validateMigrate(v: unknown): ValidationResult { return lexicons.validate('com.atproto.sync.subscribeRepos#migrate', v) } -/** Indicates that an account has been deleted. */ +/** Indicates that an account has been deleted. NOTE: may be deprecated in favor of #identity or a future #account event */ export interface Tombstone { seq: number did: string diff --git a/packages/pds/src/sequencer/db/schema.ts b/packages/pds/src/sequencer/db/schema.ts index c479c40d884..63f4911c514 100644 --- a/packages/pds/src/sequencer/db/schema.ts +++ b/packages/pds/src/sequencer/db/schema.ts @@ -5,6 +5,7 @@ export type RepoSeqEventType = | 'rebase' | 'handle' | 'migrate' + | 'identity' | 'tombstone' export interface RepoSeq { diff --git a/packages/pds/src/sequencer/events.ts b/packages/pds/src/sequencer/events.ts index 4ba9ab4d06d..12bcd0827c6 100644 --- a/packages/pds/src/sequencer/events.ts +++ b/packages/pds/src/sequencer/events.ts @@ -81,6 +81,20 @@ export const formatSeqHandleUpdate = async ( } } +export const formatSeqIdentityEvt = async ( + did: string, +): Promise => { + const evt: IdentityEvt = { + did, + } + return { + did, + eventType: 'identity', + event: cborEncode(evt), + sequencedAt: new Date().toISOString(), + } +} + export const formatSeqTombstone = async ( did: string, ): Promise => { @@ -126,6 +140,11 @@ export const handleEvt = z.object({ }) export type HandleEvt = z.infer +export const identityEvt = z.object({ + did: z.string(), +}) +export type IdentityEvt = z.infer + export const tombstoneEvt = z.object({ did: z.string(), }) @@ -143,10 +162,20 @@ type TypedHandleEvt = { time: string evt: HandleEvt } +type TypedIdentityEvt = { + type: 'identity' + seq: number + time: string + evt: IdentityEvt +} type TypedTombstoneEvt = { type: 'tombstone' seq: number time: string evt: TombstoneEvt } -export type SeqEvt = TypedCommitEvt | TypedHandleEvt | TypedTombstoneEvt +export type SeqEvt = + | TypedCommitEvt + | TypedHandleEvt + | TypedIdentityEvt + | TypedTombstoneEvt diff --git a/packages/pds/src/sequencer/sequencer.ts b/packages/pds/src/sequencer/sequencer.ts index 7ea23db13f0..444b39e195f 100644 --- a/packages/pds/src/sequencer/sequencer.ts +++ b/packages/pds/src/sequencer/sequencer.ts @@ -6,10 +6,12 @@ import { CommitData } from '@atproto/repo' import { CommitEvt, HandleEvt, + IdentityEvt, SeqEvt, TombstoneEvt, formatSeqCommit, formatSeqHandleUpdate, + formatSeqIdentityEvt, formatSeqTombstone, } from './events' import { @@ -145,6 +147,13 @@ export class Sequencer extends (EventEmitter as new () => SequencerEmitter) { time: row.sequencedAt, evt: evt as HandleEvt, }) + } else if (row.eventType === 'identity') { + seqEvts.push({ + type: 'identity', + seq: row.seq, + time: row.sequencedAt, + evt: evt as IdentityEvt, + }) } else if (row.eventType === 'tombstone') { seqEvts.push({ type: 'tombstone', @@ -209,6 +218,11 @@ export class Sequencer extends (EventEmitter as new () => SequencerEmitter) { await this.sequenceEvt(evt) } + async sequenceIdentityEvt(did: string) { + const evt = await formatSeqIdentityEvt(did) + await this.sequenceEvt(evt) + } + async sequenceTombstone(did: string) { const evt = await formatSeqTombstone(did) await this.sequenceEvt(evt) diff --git a/packages/pds/tests/sequencer.test.ts b/packages/pds/tests/sequencer.test.ts index 3d9fed7a152..82308b4db35 100644 --- a/packages/pds/tests/sequencer.test.ts +++ b/packages/pds/tests/sequencer.test.ts @@ -24,8 +24,8 @@ describe('sequencer', () => { await userSeed(sc) alice = sc.dids.alice bob = sc.dids.bob - // 6 events in userSeed - totalEvts = 6 + // 10 events in userSeed + totalEvts = 10 }) afterAll(async () => { @@ -63,10 +63,11 @@ describe('sequencer', () => { const evtToDbRow = (e: SeqEvt) => { const did = e.type === 'commit' ? e.evt.repo : e.evt.did + const eventType = e.type === 'commit' ? 'append' : e.type return { seq: e.seq, did, - eventType: 'append', + eventType, event: Buffer.from(cborEncode(e.evt)), invalidated: 0, sequencedAt: e.time, From 6dfc899d995a0a7b0eb33ea1661e5c3660e38f90 Mon Sep 17 00:00:00 2001 From: Daniel Holmgren Date: Wed, 21 Feb 2024 15:11:15 -0600 Subject: [PATCH 020/103] Enable creation of unknown record types (#2171) * start to allow 3p lexicons * tests * tidy * tests + ensure no legacy blob ref * increase the depth were willing to go when searching for blobs --- .../src/api/com/atproto/repo/applyWrites.ts | 5 - .../src/api/com/atproto/repo/createRecord.ts | 5 - .../pds/src/api/com/atproto/repo/putRecord.ts | 5 - packages/pds/src/repo/prepare.ts | 219 ++++++++---------- packages/pds/tests/crud.test.ts | 166 ++++++++++--- 5 files changed, 235 insertions(+), 165 deletions(-) diff --git a/packages/pds/src/api/com/atproto/repo/applyWrites.ts b/packages/pds/src/api/com/atproto/repo/applyWrites.ts index 16f620a30fc..d93c29fa32f 100644 --- a/packages/pds/src/api/com/atproto/repo/applyWrites.ts +++ b/packages/pds/src/api/com/atproto/repo/applyWrites.ts @@ -62,11 +62,6 @@ export default function (server: Server, ctx: AppContext) { if (did !== auth.credentials.did) { throw new AuthRequiredError() } - if (validate === false) { - throw new InvalidRequestError( - 'Unvalidated writes are not yet supported.', - ) - } if (tx.writes.length > 200) { throw new InvalidRequestError('Too many writes. Max: 200') } diff --git a/packages/pds/src/api/com/atproto/repo/createRecord.ts b/packages/pds/src/api/com/atproto/repo/createRecord.ts index 7e3aead0c56..88d3b910d41 100644 --- a/packages/pds/src/api/com/atproto/repo/createRecord.ts +++ b/packages/pds/src/api/com/atproto/repo/createRecord.ts @@ -41,11 +41,6 @@ export default function (server: Server, ctx: AppContext) { if (did !== auth.credentials.did) { throw new AuthRequiredError() } - if (validate === false) { - throw new InvalidRequestError( - 'Unvalidated writes are not yet supported.', - ) - } const swapCommitCid = swapCommit ? CID.parse(swapCommit) : undefined let write: PreparedCreate diff --git a/packages/pds/src/api/com/atproto/repo/putRecord.ts b/packages/pds/src/api/com/atproto/repo/putRecord.ts index 97ee3e4819f..4e930365ff4 100644 --- a/packages/pds/src/api/com/atproto/repo/putRecord.ts +++ b/packages/pds/src/api/com/atproto/repo/putRecord.ts @@ -51,11 +51,6 @@ export default function (server: Server, ctx: AppContext) { if (did !== auth.credentials.did) { throw new AuthRequiredError() } - if (validate === false) { - throw new InvalidRequestError( - 'Unvalidated writes are not yet supported.', - ) - } const uri = AtUri.make(did, collection, rkey) const swapCommitCid = swapCommit ? CID.parse(swapCommit) : undefined diff --git a/packages/pds/src/repo/prepare.ts b/packages/pds/src/repo/prepare.ts index 0c311462d23..ffc8cd80843 100644 --- a/packages/pds/src/repo/prepare.ts +++ b/packages/pds/src/repo/prepare.ts @@ -4,12 +4,15 @@ import { ensureValidRecordKey, ensureValidDatetime, } from '@atproto/syntax' -import { MINUTE, TID, dataToCborBlock } from '@atproto/common' +import { TID, check, dataToCborBlock } from '@atproto/common' import { + BlobRef, + LexValue, LexiconDefNotFoundError, RepoRecord, ValidationError, lexToIpld, + untypedJsonBlobRef, } from '@atproto/lexicon' import { cborToLex, @@ -28,91 +31,12 @@ import { PreparedBlobRef, } from './types' import * as lex from '../lexicon/lexicons' -import { isMain as isExternalEmbed } from '../lexicon/types/app/bsky/embed/external' -import { isMain as isImagesEmbed } from '../lexicon/types/app/bsky/embed/images' -import { isMain as isRecordWithMediaEmbed } from '../lexicon/types/app/bsky/embed/recordWithMedia' import { isRecord as isFeedGenerator } from '../lexicon/types/app/bsky/feed/generator' -import { - Record as PostRecord, - isRecord as isPost, -} from '../lexicon/types/app/bsky/feed/post' +import { isRecord as isPost } from '../lexicon/types/app/bsky/feed/post' import { isTag } from '../lexicon/types/app/bsky/richtext/facet' import { isRecord as isList } from '../lexicon/types/app/bsky/graph/list' import { isRecord as isProfile } from '../lexicon/types/app/bsky/actor/profile' import { hasExplicitSlur } from '../handle/explicit-slurs' -import { InvalidRequestError } from '@atproto/xrpc-server' - -// @TODO do this dynamically off of schemas -export const blobsForWrite = (record: unknown): PreparedBlobRef[] => { - if (isProfile(record)) { - const doc = lex.schemaDict.AppBskyActorProfile - const refs: PreparedBlobRef[] = [] - if (record.avatar) { - refs.push({ - cid: record.avatar.ref, - mimeType: record.avatar.mimeType, - constraints: doc.defs.main.record.properties.avatar, - }) - } - if (record.banner) { - refs.push({ - cid: record.banner.ref, - mimeType: record.banner.mimeType, - constraints: doc.defs.main.record.properties.banner, - }) - } - return refs - } else if (isFeedGenerator(record)) { - const doc = lex.schemaDict.AppBskyFeedGenerator - if (!record.avatar) { - return [] - } - return [ - { - cid: record.avatar.ref, - mimeType: record.avatar.mimeType, - constraints: doc.defs.main.record.properties.avatar, - }, - ] - } else if (isList(record)) { - const doc = lex.schemaDict.AppBskyGraphList - if (!record.avatar) { - return [] - } - return [ - { - cid: record.avatar.ref, - mimeType: record.avatar.mimeType, - constraints: doc.defs.main.record.properties.avatar, - }, - ] - } else if (isPost(record)) { - const refs: PreparedBlobRef[] = [] - const embeds = separateEmbeds(record.embed) - for (const embed of embeds) { - if (isImagesEmbed(embed)) { - const doc = lex.schemaDict.AppBskyEmbedImages - for (let i = 0; i < embed.images.length || 0; i++) { - const img = embed.images[i] - refs.push({ - cid: img.image.ref, - mimeType: img.image.mimeType, - constraints: doc.defs.image.properties.image, - }) - } - } else if (isExternalEmbed(embed) && embed.external.thumb) { - const doc = lex.schemaDict.AppBskyEmbedExternal - refs.push({ - cid: embed.external.thumb.ref, - mimeType: embed.external.thumb.mimeType, - constraints: doc.defs.external.properties.thumb, - }) - } - } - return refs - } - return [] -} export const assertValidRecord = (record: Record) => { if (typeof record.$type !== 'string') { @@ -180,17 +104,6 @@ export const prepareCreate = async (opts: { } const nextRkey = TID.next() - if ( - collection === lex.ids.AppBskyFeedPost && - opts.rkey && - !rkeyIsInWindow(nextRkey, new TID(opts.rkey)) - ) { - // @TODO temporary. allowing a window supports creation of post and gate records at the same time. - throw new InvalidRequestError( - 'Custom rkeys for post records should be near the present.', - ) - } - const rkey = opts.rkey || nextRkey.toString() // @TODO: validate against Lexicon record 'key' type, not just overall recordkey syntax ensureValidRecordKey(rkey) @@ -201,17 +114,10 @@ export const prepareCreate = async (opts: { cid: await cidForSafeRecord(record), swapCid, record, - blobs: blobsForWrite(record), + blobs: blobsForWrite(record, validate), } } -// only allow PUTs to certain collections -const ALLOWED_PUTS = [ - lex.ids.AppBskyActorProfile, - lex.ids.AppBskyGraphList, - lex.ids.AppBskyFeedGenerator, -] - export const prepareUpdate = async (opts: { did: string collection: string @@ -221,15 +127,6 @@ export const prepareUpdate = async (opts: { validate?: boolean }): Promise => { const { did, collection, rkey, swapCid, validate = true } = opts - if (!ALLOWED_PUTS.includes(collection)) { - // @TODO temporary - throw new InvalidRequestError( - `Temporarily only accepting updates for collections: ${ALLOWED_PUTS.join( - ', ', - )}`, - ) - } - const record = setCollectionName(collection, opts.record, validate) if (validate) { assertValidRecord(record) @@ -241,7 +138,7 @@ export const prepareUpdate = async (opts: { cid: await cidForSafeRecord(record), swapCid, record, - blobs: blobsForWrite(record), + blobs: blobsForWrite(record, validate), } } @@ -292,16 +189,6 @@ export const writeToOp = (write: PreparedWrite): RecordWriteOp => { } } -function separateEmbeds(embed: PostRecord['embed']) { - if (!embed) { - return [] - } - if (isRecordWithMediaEmbed(embed)) { - return [{ $type: lex.ids.AppBskyEmbedRecord, ...embed.record }, embed.media] - } - return [embed] -} - async function cidForSafeRecord(record: RepoRecord) { try { const block = await dataToCborBlock(lexToIpld(record)) @@ -342,8 +229,92 @@ function assertNoExplicitSlurs(rkey: string, record: RepoRecord) { } } -// ensures two rkeys are not far apart -function rkeyIsInWindow(rkey1: TID, rkey2: TID) { - const ms = Math.abs(rkey1.timestamp() - rkey2.timestamp()) / 1000 - return ms < 10 * MINUTE +type FoundBlobRef = { + ref: BlobRef + path: string[] +} + +export const blobsForWrite = ( + record: RepoRecord, + validate: boolean, +): PreparedBlobRef[] => { + const refs = findBlobRefs(record) + const recordType = + typeof record['$type'] === 'string' ? record['$type'] : undefined + + for (const ref of refs) { + if (check.is(ref.ref.original, untypedJsonBlobRef)) { + throw new InvalidRecordError(`Legacy blob ref at '${ref.path.join('/')}'`) + } + } + + return refs.map(({ ref, path }) => ({ + cid: ref.ref, + mimeType: ref.mimeType, + constraints: + validate && recordType + ? CONSTRAINTS[recordType]?.[path.join('/')] ?? {} + : {}, + })) +} + +export const findBlobRefs = ( + val: LexValue, + path: string[] = [], + layer = 0, +): FoundBlobRef[] => { + if (layer > 32) { + return [] + } + // walk arrays + if (Array.isArray(val)) { + return val.flatMap((item) => findBlobRefs(item, path, layer + 1)) + } + // objects + if (val && typeof val === 'object') { + // convert blobs, leaving the original encoding so that we don't change CIDs on re-encode + if (val instanceof BlobRef) { + return [ + { + ref: val, + path, + }, + ] + } + // retain cids & bytes + if (CID.asCID(val) || val instanceof Uint8Array) { + return [] + } + return Object.entries(val).flatMap(([key, item]) => + findBlobRefs(item, [...path, key], layer + 1), + ) + } + // pass through + return [] +} + +const CONSTRAINTS = { + [lex.ids.AppBskyActorProfile]: { + avatar: + lex.schemaDict.AppBskyActorProfile.defs.main.record.properties.avatar, + banner: + lex.schemaDict.AppBskyActorProfile.defs.main.record.properties.banner, + }, + [lex.ids.AppBskyFeedGenerator]: { + avatar: + lex.schemaDict.AppBskyFeedGenerator.defs.main.record.properties.avatar, + }, + [lex.ids.AppBskyGraphList]: { + avatar: lex.schemaDict.AppBskyGraphList.defs.main.record.properties.avatar, + }, + [lex.ids.AppBskyFeedPost]: { + 'embed/images/image': + lex.schemaDict.AppBskyEmbedImages.defs.image.properties.image, + 'embed/external/thumb': + lex.schemaDict.AppBskyEmbedExternal.defs.external.properties.thumb, + 'embed/media/images/image': + lex.schemaDict.AppBskyEmbedImages.defs.image.properties.image, + 'embed/media/external/thumb': + lex.schemaDict.AppBskyEmbedExternal.defs.external.properties.thumb, + }, } diff --git a/packages/pds/tests/crud.test.ts b/packages/pds/tests/crud.test.ts index d70da099ce3..dd3f3f3b11f 100644 --- a/packages/pds/tests/crud.test.ts +++ b/packages/pds/tests/crud.test.ts @@ -483,32 +483,6 @@ describe('crud operations', () => { expect(rootRes2.data.rev).toEqual(rootRes1.data.rev) }) - it('temporarily only allows updates to profile', async () => { - const { repo } = bobAgent.api.com.atproto - const put = await repo.putRecord({ - repo: bob.did, - collection: ids.AppBskyGraphFollow, - rkey: TID.nextStr(), - record: { - subject: alice.did, - createdAt: new Date().toISOString(), - }, - }) - const edit = repo.putRecord({ - repo: bob.did, - collection: ids.AppBskyGraphFollow, - rkey: new AtUri(put.data.uri).rkey, - record: { - subject: bob.did, - createdAt: new Date().toISOString(), - }, - }) - - await expect(edit).rejects.toThrow( - 'Temporarily only accepting updates for collections: app.bsky.actor.profile, app.bsky.graph.list, app.bsky.feed.generator', - ) - }) - it('fails on user mismatch', async () => { const { repo } = aliceAgent.api.com.atproto const put = repo.putRecord({ @@ -638,6 +612,146 @@ describe('crud operations', () => { ) }) + describe('unvalidated writes', () => { + it('disallows creation of unknown lexicons when validate is set to true', async () => { + const attempt = aliceAgent.api.com.atproto.repo.createRecord({ + repo: alice.did, + collection: 'com.example.record', + record: { + blah: 'thing', + }, + }) + await expect(attempt).rejects.toThrow( + 'Lexicon not found: lex:com.example.record', + ) + }) + + it('allows creation of unknown lexicons when validate is set to false', async () => { + const res = await aliceAgent.api.com.atproto.repo.createRecord({ + repo: alice.did, + collection: 'com.example.record', + record: { + blah: 'thing', + }, + validate: false, + }) + const record = await ctx.actorStore.read(alice.did, (store) => + store.record.getRecord(new AtUri(res.data.uri), res.data.cid), + ) + expect(record?.value).toEqual({ + $type: 'com.example.record', + blah: 'thing', + }) + }) + + it('allows update of unknown lexicons when validate is set to false', async () => { + const createRes = await aliceAgent.api.com.atproto.repo.createRecord({ + repo: alice.did, + collection: 'com.example.record', + record: { + blah: 'thing', + }, + validate: false, + }) + const uri = new AtUri(createRes.data.uri) + const updateRes = await aliceAgent.api.com.atproto.repo.putRecord({ + repo: alice.did, + collection: 'com.example.record', + rkey: uri.rkey, + record: { + blah: 'something else', + }, + validate: false, + }) + const record = await ctx.actorStore.read(alice.did, (store) => + store.record.getRecord(uri, updateRes.data.cid), + ) + expect(record?.value).toEqual({ + $type: 'com.example.record', + blah: 'something else', + }) + }) + + it('correctly associates images with unknown record types', async () => { + const file = await fs.readFile( + '../dev-env/src/seed/img/key-portrait-small.jpg', + ) + const uploadedRes = await aliceAgent.api.com.atproto.repo.uploadBlob( + file, + { + encoding: 'image/jpeg', + }, + ) + + const res = await aliceAgent.api.com.atproto.repo.createRecord({ + repo: alice.did, + collection: 'com.example.record', + record: { + blah: 'thing', + image: uploadedRes.data.blob, + }, + validate: false, + }) + const record = await ctx.actorStore.read(alice.did, (store) => + store.record.getRecord(new AtUri(res.data.uri), res.data.cid), + ) + expect(record?.value).toMatchObject({ + $type: 'com.example.record', + blah: 'thing', + }) + const recordBlobs = await ctx.actorStore.read(alice.did, (store) => + store.db.db + .selectFrom('blob') + .innerJoin('record_blob', 'record_blob.blobCid', 'blob.cid') + .where('recordUri', '=', res.data.uri) + .selectAll() + .execute(), + ) + expect(recordBlobs.length).toBe(1) + expect(recordBlobs.at(0)?.cid).toBe(uploadedRes.data.blob.ref.toString()) + }) + + it('enforces record type constraint even when unvalidated', async () => { + const attempt = aliceAgent.api.com.atproto.repo.createRecord({ + repo: alice.did, + collection: 'com.example.record', + record: { + $type: 'com.example.other', + blah: 'thing', + }, + }) + await expect(attempt).rejects.toThrow( + 'Invalid $type: expected com.example.record, got com.example.other', + ) + }) + + it('enforces blob ref format even when unvalidated', async () => { + const file = await fs.readFile( + '../dev-env/src/seed/img/key-portrait-small.jpg', + ) + const uploadedRes = await aliceAgent.api.com.atproto.repo.uploadBlob( + file, + { + encoding: 'image/jpeg', + }, + ) + + const attempt = aliceAgent.api.com.atproto.repo.createRecord({ + repo: alice.did, + collection: 'com.example.record', + record: { + blah: 'thing', + image: { + cid: uploadedRes.data.blob.ref.toString(), + mimeType: uploadedRes.data.blob.mimeType, + }, + }, + validate: false, + }) + await expect(attempt).rejects.toThrow(`Legacy blob ref at 'image'`) + }) + }) + describe('compare-and-swap', () => { let recordCount = 0 // Ensures unique cids const postRecord = () => ({ From 6b8d1c641e1534e4fea76cf05502b9d706ff571a Mon Sep 17 00:00:00 2001 From: Daniel Holmgren Date: Wed, 21 Feb 2024 15:50:05 -0600 Subject: [PATCH 021/103] Send identity evts on handle change (#2209) missed a couple --- packages/pds/src/api/com/atproto/admin/updateAccountHandle.ts | 1 + packages/pds/src/api/com/atproto/identity/updateHandle.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/pds/src/api/com/atproto/admin/updateAccountHandle.ts b/packages/pds/src/api/com/atproto/admin/updateAccountHandle.ts index 627f1aaebc9..1d4e1189c83 100644 --- a/packages/pds/src/api/com/atproto/admin/updateAccountHandle.ts +++ b/packages/pds/src/api/com/atproto/admin/updateAccountHandle.ts @@ -49,6 +49,7 @@ export default function (server: Server, ctx: AppContext) { try { await ctx.sequencer.sequenceHandleUpdate(did, handle) + await ctx.sequencer.sequenceIdentityEvt(did) } catch (err) { httpLogger.error( { err, did, handle }, diff --git a/packages/pds/src/api/com/atproto/identity/updateHandle.ts b/packages/pds/src/api/com/atproto/identity/updateHandle.ts index eb1bce9593c..06f836a83ff 100644 --- a/packages/pds/src/api/com/atproto/identity/updateHandle.ts +++ b/packages/pds/src/api/com/atproto/identity/updateHandle.ts @@ -57,6 +57,7 @@ export default function (server: Server, ctx: AppContext) { try { await ctx.sequencer.sequenceHandleUpdate(requester, handle) + await ctx.sequencer.sequenceIdentityEvt(requester) } catch (err) { httpLogger.error( { err, did: requester, handle }, From 4e0271bddfce9378755c567524b8c037a6e1c158 Mon Sep 17 00:00:00 2001 From: Daniel Holmgren Date: Wed, 21 Feb 2024 15:59:44 -0600 Subject: [PATCH 022/103] Firehose identity cleanup (#2210) * missed a couple * fix tests --- packages/pds/tests/sync/subscribe-repos.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pds/tests/sync/subscribe-repos.test.ts b/packages/pds/tests/sync/subscribe-repos.test.ts index bc877ec11ee..d6f11cc4121 100644 --- a/packages/pds/tests/sync/subscribe-repos.test.ts +++ b/packages/pds/tests/sync/subscribe-repos.test.ts @@ -302,7 +302,7 @@ describe('repo subscribe repos', () => { ws.terminate() await verifyCommitEvents(evts) - const handleEvts = getHandleEvts(evts.slice(-3)) + const handleEvts = getHandleEvts(evts.slice(-6)) verifyHandleEvent(handleEvts[0], alice, 'alice2.test') verifyHandleEvent(handleEvts[1], bob, 'bob2.test') }) @@ -318,7 +318,7 @@ describe('repo subscribe repos', () => { const evts = await readTillCaughtUp(gen, update) ws.terminate() - const handleEvts = getHandleEvts(evts.slice(-1)) + const handleEvts = getHandleEvts(evts.slice(-2)) verifyHandleEvent(handleEvts[0], bob, 'bob2.test') }) From 9f90203f20b891bc4b4daeccabe045befd7b4b1c Mon Sep 17 00:00:00 2001 From: devin ivy Date: Wed, 21 Feb 2024 20:14:45 -0500 Subject: [PATCH 023/103] Optional service config in pds distribution (#2211) * make appview and mod services optional on pds * pds: allow configuring a reporting service optionally separate from an administrative mod service * tidy --- .../src/api/app/bsky/actor/getPreferences.ts | 1 + .../pds/src/api/app/bsky/actor/getProfile.ts | 4 +- .../pds/src/api/app/bsky/actor/getProfiles.ts | 4 +- .../src/api/app/bsky/actor/getSuggestions.ts | 4 +- .../src/api/app/bsky/actor/putPreferences.ts | 1 + .../src/api/app/bsky/actor/searchActors.ts | 4 +- .../app/bsky/actor/searchActorsTypeahead.ts | 4 +- .../src/api/app/bsky/feed/getActorFeeds.ts | 4 +- .../src/api/app/bsky/feed/getActorLikes.ts | 4 +- .../src/api/app/bsky/feed/getAuthorFeed.ts | 4 +- packages/pds/src/api/app/bsky/feed/getFeed.ts | 7 ++- .../src/api/app/bsky/feed/getFeedGenerator.ts | 4 +- .../api/app/bsky/feed/getFeedGenerators.ts | 4 +- .../pds/src/api/app/bsky/feed/getLikes.ts | 4 +- .../pds/src/api/app/bsky/feed/getListFeed.ts | 4 +- .../src/api/app/bsky/feed/getPostThread.ts | 8 ++- .../pds/src/api/app/bsky/feed/getPosts.ts | 4 +- .../src/api/app/bsky/feed/getRepostedBy.ts | 4 +- .../api/app/bsky/feed/getSuggestedFeeds.ts | 4 +- .../pds/src/api/app/bsky/feed/getTimeline.ts | 4 +- .../pds/src/api/app/bsky/feed/searchPosts.ts | 4 +- .../pds/src/api/app/bsky/graph/getBlocks.ts | 4 +- .../src/api/app/bsky/graph/getFollowers.ts | 4 +- .../pds/src/api/app/bsky/graph/getFollows.ts | 4 +- .../pds/src/api/app/bsky/graph/getList.ts | 4 +- .../src/api/app/bsky/graph/getListBlocks.ts | 4 +- .../src/api/app/bsky/graph/getListMutes.ts | 4 +- .../pds/src/api/app/bsky/graph/getLists.ts | 4 +- .../pds/src/api/app/bsky/graph/getMutes.ts | 4 +- .../bsky/graph/getSuggestedFollowsByActor.ts | 4 +- .../pds/src/api/app/bsky/graph/muteActor.ts | 4 +- .../src/api/app/bsky/graph/muteActorList.ts | 4 +- .../pds/src/api/app/bsky/graph/unmuteActor.ts | 4 +- .../src/api/app/bsky/graph/unmuteActorList.ts | 4 +- .../app/bsky/notification/getUnreadCount.ts | 4 +- .../bsky/notification/listNotifications.ts | 4 +- .../api/app/bsky/notification/registerPush.ts | 15 +++-- .../api/app/bsky/notification/updateSeen.ts | 4 +- .../unspecced/getPopularFeedGenerators.ts | 4 +- .../bsky/unspecced/getTaggedSuggestions.ts | 4 +- .../admin/createCommunicationTemplate.ts | 5 +- .../admin/deleteCommunicationTemplate.ts | 4 +- .../com/atproto/admin/emitModerationEvent.ts | 5 +- .../com/atproto/admin/getModerationEvent.ts | 4 +- .../src/api/com/atproto/admin/getRecord.ts | 4 +- .../pds/src/api/com/atproto/admin/getRepo.ts | 4 +- .../admin/listCommunicationTemplates.ts | 5 +- .../atproto/admin/queryModerationEvents.ts | 4 +- .../atproto/admin/queryModerationStatuses.ts | 4 +- .../src/api/com/atproto/admin/searchRepos.ts | 4 +- .../src/api/com/atproto/admin/sendEmail.ts | 32 +++++----- .../admin/updateCommunicationTemplate.ts | 4 +- .../api/com/atproto/identity/resolveHandle.ts | 2 +- .../com/atproto/moderation/createReport.ts | 10 +++- .../pds/src/api/com/atproto/repo/getRecord.ts | 4 ++ packages/pds/src/auth-verifier.ts | 5 +- packages/pds/src/config/config.ts | 60 +++++++++++++++---- packages/pds/src/config/env.ts | 8 +++ packages/pds/src/context.ts | 38 ++++++++---- .../tests/proxied/read-after-write.test.ts | 5 +- 60 files changed, 280 insertions(+), 103 deletions(-) diff --git a/packages/pds/src/api/app/bsky/actor/getPreferences.ts b/packages/pds/src/api/app/bsky/actor/getPreferences.ts index d89666358ae..0068281a2dc 100644 --- a/packages/pds/src/api/app/bsky/actor/getPreferences.ts +++ b/packages/pds/src/api/app/bsky/actor/getPreferences.ts @@ -3,6 +3,7 @@ import AppContext from '../../../../context' import { AuthScope } from '../../../../auth-verifier' export default function (server: Server, ctx: AppContext) { + if (!ctx.cfg.bskyAppView) return server.app.bsky.actor.getPreferences({ auth: ctx.authVerifier.access, handler: async ({ auth }) => { diff --git a/packages/pds/src/api/app/bsky/actor/getProfile.ts b/packages/pds/src/api/app/bsky/actor/getProfile.ts index d772b2b2ac2..74de7f3af6d 100644 --- a/packages/pds/src/api/app/bsky/actor/getProfile.ts +++ b/packages/pds/src/api/app/bsky/actor/getProfile.ts @@ -12,13 +12,15 @@ import { pipethrough } from '../../../../pipethrough' const METHOD_NSID = 'app.bsky.actor.getProfile' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.actor.getProfile({ auth: ctx.authVerifier.accessOrRole, handler: async ({ req, auth, params }) => { const requester = auth.credentials.type === 'access' ? auth.credentials.did : null const res = await pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, METHOD_NSID, params, requester ? await ctx.appviewAuthHeaders(requester) : authPassthru(req), diff --git a/packages/pds/src/api/app/bsky/actor/getProfiles.ts b/packages/pds/src/api/app/bsky/actor/getProfiles.ts index edbc880f43d..3ab9338c7c5 100644 --- a/packages/pds/src/api/app/bsky/actor/getProfiles.ts +++ b/packages/pds/src/api/app/bsky/actor/getProfiles.ts @@ -11,13 +11,15 @@ import { const METHOD_NSID = 'app.bsky.actor.getProfiles' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.actor.getProfiles({ auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did const res = await pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, METHOD_NSID, params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/actor/getSuggestions.ts b/packages/pds/src/api/app/bsky/actor/getSuggestions.ts index 548d4f1611e..6bfd65adf74 100644 --- a/packages/pds/src/api/app/bsky/actor/getSuggestions.ts +++ b/packages/pds/src/api/app/bsky/actor/getSuggestions.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.actor.getSuggestions({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.actor.getSuggestions', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/actor/putPreferences.ts b/packages/pds/src/api/app/bsky/actor/putPreferences.ts index f6274d42278..7b3755f891e 100644 --- a/packages/pds/src/api/app/bsky/actor/putPreferences.ts +++ b/packages/pds/src/api/app/bsky/actor/putPreferences.ts @@ -4,6 +4,7 @@ import AppContext from '../../../../context' import { AccountPreference } from '../../../../actor-store/preference/reader' export default function (server: Server, ctx: AppContext) { + if (!ctx.cfg.bskyAppView) return server.app.bsky.actor.putPreferences({ auth: ctx.authVerifier.accessCheckTakedown, handler: async ({ auth, input }) => { diff --git a/packages/pds/src/api/app/bsky/actor/searchActors.ts b/packages/pds/src/api/app/bsky/actor/searchActors.ts index 57f502acb3c..53c97566818 100644 --- a/packages/pds/src/api/app/bsky/actor/searchActors.ts +++ b/packages/pds/src/api/app/bsky/actor/searchActors.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.actor.searchActors({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.actor.searchActors', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/actor/searchActorsTypeahead.ts b/packages/pds/src/api/app/bsky/actor/searchActorsTypeahead.ts index dbf57900509..51e778b24ee 100644 --- a/packages/pds/src/api/app/bsky/actor/searchActorsTypeahead.ts +++ b/packages/pds/src/api/app/bsky/actor/searchActorsTypeahead.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.actor.searchActorsTypeahead({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.actor.searchActorsTypeahead', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/feed/getActorFeeds.ts b/packages/pds/src/api/app/bsky/feed/getActorFeeds.ts index ca5d8d5d48e..123bce1785b 100644 --- a/packages/pds/src/api/app/bsky/feed/getActorFeeds.ts +++ b/packages/pds/src/api/app/bsky/feed/getActorFeeds.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.feed.getActorFeeds({ auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.feed.getActorFeeds', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/feed/getActorLikes.ts b/packages/pds/src/api/app/bsky/feed/getActorLikes.ts index 4ba8bb49f49..d8e2f839904 100644 --- a/packages/pds/src/api/app/bsky/feed/getActorLikes.ts +++ b/packages/pds/src/api/app/bsky/feed/getActorLikes.ts @@ -12,13 +12,15 @@ import { pipethrough } from '../../../../pipethrough' const METHOD_NSID = 'app.bsky.feed.getActorLikes' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.feed.getActorLikes({ auth: ctx.authVerifier.accessOrRole, handler: async ({ req, params, auth }) => { const requester = auth.credentials.type === 'access' ? auth.credentials.did : null const res = await pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, METHOD_NSID, params, requester ? await ctx.appviewAuthHeaders(requester) : authPassthru(req), diff --git a/packages/pds/src/api/app/bsky/feed/getAuthorFeed.ts b/packages/pds/src/api/app/bsky/feed/getAuthorFeed.ts index f142a6c34e9..c90760bbfd9 100644 --- a/packages/pds/src/api/app/bsky/feed/getAuthorFeed.ts +++ b/packages/pds/src/api/app/bsky/feed/getAuthorFeed.ts @@ -13,13 +13,15 @@ import { pipethrough } from '../../../../pipethrough' const METHOD_NSID = 'app.bsky.feed.getAuthorFeed' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.feed.getAuthorFeed({ auth: ctx.authVerifier.accessOrRole, handler: async ({ req, params, auth }) => { const requester = auth.credentials.type === 'access' ? auth.credentials.did : null const res = await pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, METHOD_NSID, params, requester ? await ctx.appviewAuthHeaders(requester) : authPassthru(req), diff --git a/packages/pds/src/api/app/bsky/feed/getFeed.ts b/packages/pds/src/api/app/bsky/feed/getFeed.ts index ea63cf1bd47..89c84b18ba8 100644 --- a/packages/pds/src/api/app/bsky/feed/getFeed.ts +++ b/packages/pds/src/api/app/bsky/feed/getFeed.ts @@ -3,13 +3,16 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { appViewAgent } = ctx + const { bskyAppView } = ctx.cfg + if (!appViewAgent || !bskyAppView) return server.app.bsky.feed.getFeed({ auth: ctx.authVerifier.access, handler: async ({ req, params, auth }) => { const requester = auth.credentials.did const { data: feed } = - await ctx.appViewAgent.api.app.bsky.feed.getFeedGenerator( + await appViewAgent.api.app.bsky.feed.getFeedGenerator( { feed: params.feed }, await ctx.appviewAuthHeaders(requester), ) @@ -21,7 +24,7 @@ export default function (server: Server, ctx: AppContext) { serviceAuthHeaders.headers['accept-language'] = req.headers['accept-language'] return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.feed.getFeed', params, serviceAuthHeaders, diff --git a/packages/pds/src/api/app/bsky/feed/getFeedGenerator.ts b/packages/pds/src/api/app/bsky/feed/getFeedGenerator.ts index deb5025f0fe..f71ea74117f 100644 --- a/packages/pds/src/api/app/bsky/feed/getFeedGenerator.ts +++ b/packages/pds/src/api/app/bsky/feed/getFeedGenerator.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.feed.getFeedGenerator({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.feed.getFeedGenerator', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/feed/getFeedGenerators.ts b/packages/pds/src/api/app/bsky/feed/getFeedGenerators.ts index 12755d0feea..c07c3dac228 100644 --- a/packages/pds/src/api/app/bsky/feed/getFeedGenerators.ts +++ b/packages/pds/src/api/app/bsky/feed/getFeedGenerators.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.feed.getFeedGenerators({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.feed.getFeedGenerators', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/feed/getLikes.ts b/packages/pds/src/api/app/bsky/feed/getLikes.ts index cb5f2cd6fbc..90a96681c85 100644 --- a/packages/pds/src/api/app/bsky/feed/getLikes.ts +++ b/packages/pds/src/api/app/bsky/feed/getLikes.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.feed.getLikes({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.feed.getLikes', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/feed/getListFeed.ts b/packages/pds/src/api/app/bsky/feed/getListFeed.ts index cbbc757dd52..3447a721904 100644 --- a/packages/pds/src/api/app/bsky/feed/getListFeed.ts +++ b/packages/pds/src/api/app/bsky/feed/getListFeed.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.feed.getListFeed({ auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.feed.getListFeed', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/feed/getPostThread.ts b/packages/pds/src/api/app/bsky/feed/getPostThread.ts index d4aec88970a..da09523875b 100644 --- a/packages/pds/src/api/app/bsky/feed/getPostThread.ts +++ b/packages/pds/src/api/app/bsky/feed/getPostThread.ts @@ -1,3 +1,4 @@ +import assert from 'node:assert' import { AtUri } from '@atproto/syntax' import { Headers, XRPCError } from '@atproto/xrpc' import { Server } from '../../../../lexicon' @@ -26,6 +27,8 @@ import { pipethrough } from '../../../../pipethrough' const METHOD_NSID = 'app.bsky.feed.getPostThread' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.feed.getPostThread({ auth: ctx.authVerifier.accessOrRole, handler: async ({ req, params, auth }) => { @@ -34,7 +37,7 @@ export default function (server: Server, ctx: AppContext) { if (!requester) { return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, METHOD_NSID, params, authPassthru(req), @@ -43,7 +46,7 @@ export default function (server: Server, ctx: AppContext) { try { const res = await pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, METHOD_NSID, params, await ctx.appviewAuthHeaders(requester), @@ -200,6 +203,7 @@ const readAfterWriteNotFound = async ( const highestParent = getHighestParent(thread) if (highestParent) { try { + assert(ctx.appViewAgent) const parentsRes = await ctx.appViewAgent.api.app.bsky.feed.getPostThread( { uri: highestParent, parentHeight: params.parentHeight, depth: 0 }, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/feed/getPosts.ts b/packages/pds/src/api/app/bsky/feed/getPosts.ts index 5caa986f9f4..89d0d08587d 100644 --- a/packages/pds/src/api/app/bsky/feed/getPosts.ts +++ b/packages/pds/src/api/app/bsky/feed/getPosts.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.feed.getPosts({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.feed.getPosts', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/feed/getRepostedBy.ts b/packages/pds/src/api/app/bsky/feed/getRepostedBy.ts index dcb00d5ce64..971d150824c 100644 --- a/packages/pds/src/api/app/bsky/feed/getRepostedBy.ts +++ b/packages/pds/src/api/app/bsky/feed/getRepostedBy.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.feed.getRepostedBy({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.feed.getRepostedBy', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/feed/getSuggestedFeeds.ts b/packages/pds/src/api/app/bsky/feed/getSuggestedFeeds.ts index ee1161ca0ff..6da81787533 100644 --- a/packages/pds/src/api/app/bsky/feed/getSuggestedFeeds.ts +++ b/packages/pds/src/api/app/bsky/feed/getSuggestedFeeds.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.feed.getSuggestedFeeds({ auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.feed.getSuggestedFeeds', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/feed/getTimeline.ts b/packages/pds/src/api/app/bsky/feed/getTimeline.ts index ffce48244b5..90fc5bac42f 100644 --- a/packages/pds/src/api/app/bsky/feed/getTimeline.ts +++ b/packages/pds/src/api/app/bsky/feed/getTimeline.ts @@ -11,12 +11,14 @@ import { pipethrough } from '../../../../pipethrough' const METHOD_NSID = 'app.bsky.feed.getTimeline' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.feed.getTimeline({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did const res = await pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, METHOD_NSID, params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/feed/searchPosts.ts b/packages/pds/src/api/app/bsky/feed/searchPosts.ts index 24bc33f6034..7cc09c864e5 100644 --- a/packages/pds/src/api/app/bsky/feed/searchPosts.ts +++ b/packages/pds/src/api/app/bsky/feed/searchPosts.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.feed.searchPosts({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.feed.searchPosts', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/graph/getBlocks.ts b/packages/pds/src/api/app/bsky/graph/getBlocks.ts index f50066e5e8f..1b29f9b62d2 100644 --- a/packages/pds/src/api/app/bsky/graph/getBlocks.ts +++ b/packages/pds/src/api/app/bsky/graph/getBlocks.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.graph.getBlocks({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.graph.getBlocks', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/graph/getFollowers.ts b/packages/pds/src/api/app/bsky/graph/getFollowers.ts index 4a07284b525..0a158f2bbe5 100644 --- a/packages/pds/src/api/app/bsky/graph/getFollowers.ts +++ b/packages/pds/src/api/app/bsky/graph/getFollowers.ts @@ -4,13 +4,15 @@ import { authPassthru } from '../../../proxy' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.graph.getFollowers({ auth: ctx.authVerifier.accessOrRole, handler: async ({ req, params, auth }) => { const requester = auth.credentials.type === 'access' ? auth.credentials.did : null return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.graph.getFollowers', params, requester ? await ctx.appviewAuthHeaders(requester) : authPassthru(req), diff --git a/packages/pds/src/api/app/bsky/graph/getFollows.ts b/packages/pds/src/api/app/bsky/graph/getFollows.ts index 2f3c2f2041a..6802acda888 100644 --- a/packages/pds/src/api/app/bsky/graph/getFollows.ts +++ b/packages/pds/src/api/app/bsky/graph/getFollows.ts @@ -4,13 +4,15 @@ import { authPassthru } from '../../../proxy' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.graph.getFollows({ auth: ctx.authVerifier.accessOrRole, handler: async ({ req, params, auth }) => { const requester = auth.credentials.type === 'access' ? auth.credentials.did : null return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.graph.getFollows', params, requester ? await ctx.appviewAuthHeaders(requester) : authPassthru(req), diff --git a/packages/pds/src/api/app/bsky/graph/getList.ts b/packages/pds/src/api/app/bsky/graph/getList.ts index 60ea41cfd2f..6ef1dbf7ee0 100644 --- a/packages/pds/src/api/app/bsky/graph/getList.ts +++ b/packages/pds/src/api/app/bsky/graph/getList.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.graph.getList({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.graph.getList', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/graph/getListBlocks.ts b/packages/pds/src/api/app/bsky/graph/getListBlocks.ts index fed639e1dc8..d9aed6e7cd6 100644 --- a/packages/pds/src/api/app/bsky/graph/getListBlocks.ts +++ b/packages/pds/src/api/app/bsky/graph/getListBlocks.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.graph.getListBlocks({ auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.graph.getListBlocks', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/graph/getListMutes.ts b/packages/pds/src/api/app/bsky/graph/getListMutes.ts index f3856a7fdc0..575c09d5b1a 100644 --- a/packages/pds/src/api/app/bsky/graph/getListMutes.ts +++ b/packages/pds/src/api/app/bsky/graph/getListMutes.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.graph.getListMutes({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.graph.getListMutes', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/graph/getLists.ts b/packages/pds/src/api/app/bsky/graph/getLists.ts index 4af580e280c..c824c9cdb4b 100644 --- a/packages/pds/src/api/app/bsky/graph/getLists.ts +++ b/packages/pds/src/api/app/bsky/graph/getLists.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.graph.getLists({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.graph.getLists', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/graph/getMutes.ts b/packages/pds/src/api/app/bsky/graph/getMutes.ts index 103c0bf4f2f..d422237dd0f 100644 --- a/packages/pds/src/api/app/bsky/graph/getMutes.ts +++ b/packages/pds/src/api/app/bsky/graph/getMutes.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.graph.getMutes({ auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.graph.getMutes', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts b/packages/pds/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts index b4422b7090e..dfe453be8f6 100644 --- a/packages/pds/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts +++ b/packages/pds/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.graph.getSuggestedFollowsByActor({ auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.graph.getSuggestedFollowsByActor', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/graph/muteActor.ts b/packages/pds/src/api/app/bsky/graph/muteActor.ts index 2b2f218b44d..c88a05b9eaf 100644 --- a/packages/pds/src/api/app/bsky/graph/muteActor.ts +++ b/packages/pds/src/api/app/bsky/graph/muteActor.ts @@ -2,12 +2,14 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' export default function (server: Server, ctx: AppContext) { + const { appViewAgent } = ctx + if (!appViewAgent) return server.app.bsky.graph.muteActor({ auth: ctx.authVerifier.access, handler: async ({ auth, input }) => { const requester = auth.credentials.did - await ctx.appViewAgent.api.app.bsky.graph.muteActor(input.body, { + await appViewAgent.api.app.bsky.graph.muteActor(input.body, { ...(await ctx.appviewAuthHeaders(requester)), encoding: 'application/json', }) diff --git a/packages/pds/src/api/app/bsky/graph/muteActorList.ts b/packages/pds/src/api/app/bsky/graph/muteActorList.ts index 97d524900f7..74c2357d3d9 100644 --- a/packages/pds/src/api/app/bsky/graph/muteActorList.ts +++ b/packages/pds/src/api/app/bsky/graph/muteActorList.ts @@ -2,12 +2,14 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' export default function (server: Server, ctx: AppContext) { + const { appViewAgent } = ctx + if (!appViewAgent) return server.app.bsky.graph.muteActorList({ auth: ctx.authVerifier.access, handler: async ({ auth, input }) => { const requester = auth.credentials.did - await ctx.appViewAgent.api.app.bsky.graph.muteActorList(input.body, { + await appViewAgent.api.app.bsky.graph.muteActorList(input.body, { ...(await ctx.appviewAuthHeaders(requester)), encoding: 'application/json', }) diff --git a/packages/pds/src/api/app/bsky/graph/unmuteActor.ts b/packages/pds/src/api/app/bsky/graph/unmuteActor.ts index 0f7a1610321..e73c5d08e5a 100644 --- a/packages/pds/src/api/app/bsky/graph/unmuteActor.ts +++ b/packages/pds/src/api/app/bsky/graph/unmuteActor.ts @@ -2,12 +2,14 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' export default function (server: Server, ctx: AppContext) { + const { appViewAgent } = ctx + if (!appViewAgent) return server.app.bsky.graph.unmuteActor({ auth: ctx.authVerifier.access, handler: async ({ auth, input }) => { const requester = auth.credentials.did - await ctx.appViewAgent.api.app.bsky.graph.unmuteActor(input.body, { + await appViewAgent.api.app.bsky.graph.unmuteActor(input.body, { ...(await ctx.appviewAuthHeaders(requester)), encoding: 'application/json', }) diff --git a/packages/pds/src/api/app/bsky/graph/unmuteActorList.ts b/packages/pds/src/api/app/bsky/graph/unmuteActorList.ts index aaf5225bded..e36afeaf0a3 100644 --- a/packages/pds/src/api/app/bsky/graph/unmuteActorList.ts +++ b/packages/pds/src/api/app/bsky/graph/unmuteActorList.ts @@ -2,12 +2,14 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' export default function (server: Server, ctx: AppContext) { + const { appViewAgent } = ctx + if (!appViewAgent) return server.app.bsky.graph.unmuteActorList({ auth: ctx.authVerifier.access, handler: async ({ auth, input }) => { const requester = auth.credentials.did - await ctx.appViewAgent.api.app.bsky.graph.unmuteActorList(input.body, { + await appViewAgent.api.app.bsky.graph.unmuteActorList(input.body, { ...(await ctx.appviewAuthHeaders(requester)), encoding: 'application/json', }) diff --git a/packages/pds/src/api/app/bsky/notification/getUnreadCount.ts b/packages/pds/src/api/app/bsky/notification/getUnreadCount.ts index 0827f47bb9e..d6b8a235ba3 100644 --- a/packages/pds/src/api/app/bsky/notification/getUnreadCount.ts +++ b/packages/pds/src/api/app/bsky/notification/getUnreadCount.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.notification.getUnreadCount({ auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.notification.getUnreadCount', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/notification/listNotifications.ts b/packages/pds/src/api/app/bsky/notification/listNotifications.ts index ad5d8635b41..005473eb6f4 100644 --- a/packages/pds/src/api/app/bsky/notification/listNotifications.ts +++ b/packages/pds/src/api/app/bsky/notification/listNotifications.ts @@ -3,12 +3,14 @@ import AppContext from '../../../../context' import { pipethrough } from '../../../../pipethrough' export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.notification.listNotifications({ auth: ctx.authVerifier.access, handler: async ({ params, auth }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.notification.listNotifications', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/notification/registerPush.ts b/packages/pds/src/api/app/bsky/notification/registerPush.ts index efe26ce9366..ec6084c41aa 100644 --- a/packages/pds/src/api/app/bsky/notification/registerPush.ts +++ b/packages/pds/src/api/app/bsky/notification/registerPush.ts @@ -6,6 +6,8 @@ import { AtpAgent } from '@atproto/api' import { getDidDoc } from '../util/resolver' export default function (server: Server, ctx: AppContext) { + const { appViewAgent } = ctx + if (!appViewAgent) return server.app.bsky.notification.registerPush({ auth: ctx.authVerifier.accessDeactived, handler: async ({ auth, input }) => { @@ -16,14 +18,11 @@ export default function (server: Server, ctx: AppContext) { const authHeaders = await ctx.serviceAuthHeaders(did, serviceDid) - if (ctx.cfg.bskyAppView.did === serviceDid) { - await ctx.appViewAgent.api.app.bsky.notification.registerPush( - input.body, - { - ...authHeaders, - encoding: 'application/json', - }, - ) + if (ctx.cfg.bskyAppView?.did === serviceDid) { + await appViewAgent.api.app.bsky.notification.registerPush(input.body, { + ...authHeaders, + encoding: 'application/json', + }) return } diff --git a/packages/pds/src/api/app/bsky/notification/updateSeen.ts b/packages/pds/src/api/app/bsky/notification/updateSeen.ts index dc0217ffb67..18a0ea3fa11 100644 --- a/packages/pds/src/api/app/bsky/notification/updateSeen.ts +++ b/packages/pds/src/api/app/bsky/notification/updateSeen.ts @@ -2,12 +2,14 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' export default function (server: Server, ctx: AppContext) { + const { appViewAgent } = ctx + if (!appViewAgent) return server.app.bsky.notification.updateSeen({ auth: ctx.authVerifier.access, handler: async ({ input, auth }) => { const requester = auth.credentials.did - await ctx.appViewAgent.api.app.bsky.notification.updateSeen(input.body, { + await appViewAgent.api.app.bsky.notification.updateSeen(input.body, { ...(await ctx.appviewAuthHeaders(requester)), encoding: 'application/json', }) diff --git a/packages/pds/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts b/packages/pds/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts index 56f0c6134af..da7be6fb649 100644 --- a/packages/pds/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +++ b/packages/pds/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts @@ -4,12 +4,14 @@ import { pipethrough } from '../../../../pipethrough' // THIS IS A TEMPORARY UNSPECCED ROUTE export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.unspecced.getPopularFeedGenerators({ auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.unspecced.getPopularFeedGenerators', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/app/bsky/unspecced/getTaggedSuggestions.ts b/packages/pds/src/api/app/bsky/unspecced/getTaggedSuggestions.ts index 80c868490c4..68e84985441 100644 --- a/packages/pds/src/api/app/bsky/unspecced/getTaggedSuggestions.ts +++ b/packages/pds/src/api/app/bsky/unspecced/getTaggedSuggestions.ts @@ -4,12 +4,14 @@ import { pipethrough } from '../../../../pipethrough' // THIS IS A TEMPORARY UNSPECCED ROUTE export default function (server: Server, ctx: AppContext) { + const { bskyAppView } = ctx.cfg + if (!bskyAppView) return server.app.bsky.unspecced.getTaggedSuggestions({ auth: ctx.authVerifier.access, handler: async ({ auth, params }) => { const requester = auth.credentials.did return pipethrough( - ctx.cfg.bskyAppView.url, + bskyAppView.url, 'app.bsky.unspecced.getTaggedSuggestions', params, await ctx.appviewAuthHeaders(requester), diff --git a/packages/pds/src/api/com/atproto/admin/createCommunicationTemplate.ts b/packages/pds/src/api/com/atproto/admin/createCommunicationTemplate.ts index 5d1c9bf1ee1..7b6939270a2 100644 --- a/packages/pds/src/api/com/atproto/admin/createCommunicationTemplate.ts +++ b/packages/pds/src/api/com/atproto/admin/createCommunicationTemplate.ts @@ -3,15 +3,16 @@ import AppContext from '../../../../context' import { authPassthru } from '../../../proxy' export default function (server: Server, ctx: AppContext) { + const { moderationAgent } = ctx + if (!moderationAgent) return server.com.atproto.admin.createCommunicationTemplate({ auth: ctx.authVerifier.role, handler: async ({ req, input }) => { const { data: result } = - await ctx.moderationAgent.com.atproto.admin.createCommunicationTemplate( + await moderationAgent.com.atproto.admin.createCommunicationTemplate( input.body, authPassthru(req, true), ) - return { encoding: 'application/json', body: result, diff --git a/packages/pds/src/api/com/atproto/admin/deleteCommunicationTemplate.ts b/packages/pds/src/api/com/atproto/admin/deleteCommunicationTemplate.ts index 2f497f9089e..d10c2564571 100644 --- a/packages/pds/src/api/com/atproto/admin/deleteCommunicationTemplate.ts +++ b/packages/pds/src/api/com/atproto/admin/deleteCommunicationTemplate.ts @@ -3,10 +3,12 @@ import AppContext from '../../../../context' import { authPassthru } from '../../../proxy' export default function (server: Server, ctx: AppContext) { + const { moderationAgent } = ctx + if (!moderationAgent) return server.com.atproto.admin.deleteCommunicationTemplate({ auth: ctx.authVerifier.role, handler: async ({ req, input }) => { - await ctx.moderationAgent.com.atproto.admin.deleteCommunicationTemplate( + await moderationAgent.com.atproto.admin.deleteCommunicationTemplate( input.body, authPassthru(req, true), ) diff --git a/packages/pds/src/api/com/atproto/admin/emitModerationEvent.ts b/packages/pds/src/api/com/atproto/admin/emitModerationEvent.ts index b2befdd53cc..65bb4c36d0e 100644 --- a/packages/pds/src/api/com/atproto/admin/emitModerationEvent.ts +++ b/packages/pds/src/api/com/atproto/admin/emitModerationEvent.ts @@ -3,15 +3,16 @@ import AppContext from '../../../../context' import { authPassthru } from '../../../proxy' export default function (server: Server, ctx: AppContext) { + const { moderationAgent } = ctx + if (!moderationAgent) return server.com.atproto.admin.emitModerationEvent({ auth: ctx.authVerifier.role, handler: async ({ req, input }) => { const { data: result } = - await ctx.moderationAgent.com.atproto.admin.emitModerationEvent( + await moderationAgent.com.atproto.admin.emitModerationEvent( input.body, authPassthru(req, true), ) - return { encoding: 'application/json', body: result, diff --git a/packages/pds/src/api/com/atproto/admin/getModerationEvent.ts b/packages/pds/src/api/com/atproto/admin/getModerationEvent.ts index d368c3bfd72..a5e579baa58 100644 --- a/packages/pds/src/api/com/atproto/admin/getModerationEvent.ts +++ b/packages/pds/src/api/com/atproto/admin/getModerationEvent.ts @@ -3,11 +3,13 @@ import AppContext from '../../../../context' import { authPassthru } from '../../../proxy' export default function (server: Server, ctx: AppContext) { + const { moderationAgent } = ctx + if (!moderationAgent) return server.com.atproto.admin.getModerationEvent({ auth: ctx.authVerifier.role, handler: async ({ req, params }) => { const { data } = - await ctx.moderationAgent.com.atproto.admin.getModerationEvent( + await moderationAgent.com.atproto.admin.getModerationEvent( params, authPassthru(req), ) diff --git a/packages/pds/src/api/com/atproto/admin/getRecord.ts b/packages/pds/src/api/com/atproto/admin/getRecord.ts index 90575354028..3cff5508683 100644 --- a/packages/pds/src/api/com/atproto/admin/getRecord.ts +++ b/packages/pds/src/api/com/atproto/admin/getRecord.ts @@ -3,11 +3,13 @@ import AppContext from '../../../../context' import { authPassthru } from '../../../proxy' export default function (server: Server, ctx: AppContext) { + const { moderationAgent } = ctx + if (!moderationAgent) return server.com.atproto.admin.getRecord({ auth: ctx.authVerifier.role, handler: async ({ req, params }) => { const { data: recordDetailAppview } = - await ctx.moderationAgent.com.atproto.admin.getRecord( + await moderationAgent.com.atproto.admin.getRecord( params, authPassthru(req), ) diff --git a/packages/pds/src/api/com/atproto/admin/getRepo.ts b/packages/pds/src/api/com/atproto/admin/getRepo.ts index 85592c52b14..880b407ce79 100644 --- a/packages/pds/src/api/com/atproto/admin/getRepo.ts +++ b/packages/pds/src/api/com/atproto/admin/getRepo.ts @@ -3,10 +3,12 @@ import AppContext from '../../../../context' import { authPassthru } from '../../../proxy' export default function (server: Server, ctx: AppContext) { + const { moderationAgent } = ctx + if (!moderationAgent) return server.com.atproto.admin.getRepo({ auth: ctx.authVerifier.role, handler: async ({ req, params }) => { - const res = await ctx.moderationAgent.com.atproto.admin.getRepo( + const res = await moderationAgent.com.atproto.admin.getRepo( params, authPassthru(req), ) diff --git a/packages/pds/src/api/com/atproto/admin/listCommunicationTemplates.ts b/packages/pds/src/api/com/atproto/admin/listCommunicationTemplates.ts index 41e5d025522..dfe3a74bce8 100644 --- a/packages/pds/src/api/com/atproto/admin/listCommunicationTemplates.ts +++ b/packages/pds/src/api/com/atproto/admin/listCommunicationTemplates.ts @@ -3,15 +3,16 @@ import AppContext from '../../../../context' import { authPassthru } from '../../../proxy' export default function (server: Server, ctx: AppContext) { + const { moderationAgent } = ctx + if (!moderationAgent) return server.com.atproto.admin.listCommunicationTemplates({ auth: ctx.authVerifier.role, handler: async ({ req }) => { const { data: result } = - await ctx.moderationAgent.com.atproto.admin.listCommunicationTemplates( + await moderationAgent.com.atproto.admin.listCommunicationTemplates( {}, authPassthru(req, true), ) - return { encoding: 'application/json', body: result, diff --git a/packages/pds/src/api/com/atproto/admin/queryModerationEvents.ts b/packages/pds/src/api/com/atproto/admin/queryModerationEvents.ts index 00e12439649..2d33ca6d466 100644 --- a/packages/pds/src/api/com/atproto/admin/queryModerationEvents.ts +++ b/packages/pds/src/api/com/atproto/admin/queryModerationEvents.ts @@ -3,11 +3,13 @@ import AppContext from '../../../../context' import { authPassthru } from '../../../proxy' export default function (server: Server, ctx: AppContext) { + const { moderationAgent } = ctx + if (!moderationAgent) return server.com.atproto.admin.queryModerationEvents({ auth: ctx.authVerifier.role, handler: async ({ req, params }) => { const { data: result } = - await ctx.moderationAgent.com.atproto.admin.queryModerationEvents( + await moderationAgent.com.atproto.admin.queryModerationEvents( params, authPassthru(req), ) diff --git a/packages/pds/src/api/com/atproto/admin/queryModerationStatuses.ts b/packages/pds/src/api/com/atproto/admin/queryModerationStatuses.ts index d2b2f36a1fe..c31125ce114 100644 --- a/packages/pds/src/api/com/atproto/admin/queryModerationStatuses.ts +++ b/packages/pds/src/api/com/atproto/admin/queryModerationStatuses.ts @@ -3,11 +3,13 @@ import AppContext from '../../../../context' import { authPassthru } from '../../../proxy' export default function (server: Server, ctx: AppContext) { + const { moderationAgent } = ctx + if (!moderationAgent) return server.com.atproto.admin.queryModerationStatuses({ auth: ctx.authVerifier.role, handler: async ({ req, params }) => { const { data } = - await ctx.moderationAgent.com.atproto.admin.queryModerationStatuses( + await moderationAgent.com.atproto.admin.queryModerationStatuses( params, authPassthru(req), ) diff --git a/packages/pds/src/api/com/atproto/admin/searchRepos.ts b/packages/pds/src/api/com/atproto/admin/searchRepos.ts index 5e21b2ab894..d09ff7b2327 100644 --- a/packages/pds/src/api/com/atproto/admin/searchRepos.ts +++ b/packages/pds/src/api/com/atproto/admin/searchRepos.ts @@ -3,11 +3,13 @@ import AppContext from '../../../../context' import { authPassthru } from '../../../proxy' export default function (server: Server, ctx: AppContext) { + const { moderationAgent } = ctx + if (!moderationAgent) return server.com.atproto.admin.searchRepos({ auth: ctx.authVerifier.role, handler: async ({ req, params }) => { const { data: result } = - await ctx.moderationAgent.com.atproto.admin.searchRepos( + await moderationAgent.com.atproto.admin.searchRepos( params, authPassthru(req), ) diff --git a/packages/pds/src/api/com/atproto/admin/sendEmail.ts b/packages/pds/src/api/com/atproto/admin/sendEmail.ts index b61e9e0d158..e23d6bea5c1 100644 --- a/packages/pds/src/api/com/atproto/admin/sendEmail.ts +++ b/packages/pds/src/api/com/atproto/admin/sendEmail.ts @@ -43,21 +43,25 @@ export default function (server: Server, ctx: AppContext) { { content }, { subject, to: account.email }, ) - await ctx.moderationAgent.api.com.atproto.admin.emitModerationEvent( - { - event: { - $type: 'com.atproto.admin.defs#modEventEmail', - subjectLine: subject, - comment, - }, - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: recipientDid, + + if (ctx.moderationAgent) { + await ctx.moderationAgent.api.com.atproto.admin.emitModerationEvent( + { + event: { + $type: 'com.atproto.admin.defs#modEventEmail', + subjectLine: subject, + comment, + }, + subject: { + $type: 'com.atproto.admin.defs#repoRef', + did: recipientDid, + }, + createdBy: senderDid, }, - createdBy: senderDid, - }, - { ...authPassthru(req), encoding: 'application/json' }, - ) + { ...authPassthru(req), encoding: 'application/json' }, + ) + } + return { encoding: 'application/json', body: { sent: true }, diff --git a/packages/pds/src/api/com/atproto/admin/updateCommunicationTemplate.ts b/packages/pds/src/api/com/atproto/admin/updateCommunicationTemplate.ts index abec4b275e6..c548a83bf03 100644 --- a/packages/pds/src/api/com/atproto/admin/updateCommunicationTemplate.ts +++ b/packages/pds/src/api/com/atproto/admin/updateCommunicationTemplate.ts @@ -3,11 +3,13 @@ import AppContext from '../../../../context' import { authPassthru } from '../../../proxy' export default function (server: Server, ctx: AppContext) { + const { moderationAgent } = ctx + if (!moderationAgent) return server.com.atproto.admin.updateCommunicationTemplate({ auth: ctx.authVerifier.role, handler: async ({ req, input }) => { const { data: result } = - await ctx.moderationAgent.com.atproto.admin.updateCommunicationTemplate( + await moderationAgent.com.atproto.admin.updateCommunicationTemplate( input.body, authPassthru(req, true), ) diff --git a/packages/pds/src/api/com/atproto/identity/resolveHandle.ts b/packages/pds/src/api/com/atproto/identity/resolveHandle.ts index 67e0af41520..40122e2f3b6 100644 --- a/packages/pds/src/api/com/atproto/identity/resolveHandle.ts +++ b/packages/pds/src/api/com/atproto/identity/resolveHandle.ts @@ -33,7 +33,7 @@ export default function (server: Server, ctx: AppContext) { } // this is not someone on our server, but we help with resolving anyway - if (!did) { + if (!did && ctx.appViewAgent) { did = await tryResolveFromAppView(ctx.appViewAgent, handle) } diff --git a/packages/pds/src/api/com/atproto/moderation/createReport.ts b/packages/pds/src/api/com/atproto/moderation/createReport.ts index 7b3cc998e22..64ed5c20005 100644 --- a/packages/pds/src/api/com/atproto/moderation/createReport.ts +++ b/packages/pds/src/api/com/atproto/moderation/createReport.ts @@ -1,16 +1,22 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { InvalidRequestError } from '@atproto/xrpc-server' export default function (server: Server, ctx: AppContext) { server.com.atproto.moderation.createReport({ auth: ctx.authVerifier.accessCheckTakedown, handler: async ({ input, auth }) => { const requester = auth.credentials.did + if (!ctx.reportingAgent) { + throw new InvalidRequestError( + 'Your hosting service is not configured with a moderation provider. If this seems in error, reach out to your hosting provider.', + ) + } const { data: result } = - await ctx.moderationAgent.com.atproto.moderation.createReport( + await ctx.reportingAgent.com.atproto.moderation.createReport( input.body, { - ...(await ctx.moderationAuthHeaders(requester)), + ...(await ctx.reportingAuthHeaders(requester)), encoding: 'application/json', }, ) diff --git a/packages/pds/src/api/com/atproto/repo/getRecord.ts b/packages/pds/src/api/com/atproto/repo/getRecord.ts index 20abb3c46a8..3d8b44099d4 100644 --- a/packages/pds/src/api/com/atproto/repo/getRecord.ts +++ b/packages/pds/src/api/com/atproto/repo/getRecord.ts @@ -28,6 +28,10 @@ export default function (server: Server, ctx: AppContext) { } } + if (!ctx.cfg.bskyAppView) { + throw new InvalidRequestError(`Could not locate record`) + } + return await pipethrough( ctx.cfg.bskyAppView.url, 'com.atproto.repo.getRecord', diff --git a/packages/pds/src/auth-verifier.ts b/packages/pds/src/auth-verifier.ts index 9d1f7b8bd19..668791c187f 100644 --- a/packages/pds/src/auth-verifier.ts +++ b/packages/pds/src/auth-verifier.ts @@ -97,7 +97,7 @@ export type AuthVerifierOpts = { dids: { pds: string entryway?: string - admin: string + admin?: string } } @@ -254,6 +254,9 @@ export class AuthVerifier { } adminService = async (reqCtx: ReqCtx): Promise => { + if (!this.dids.admin) { + throw new AuthRequiredError('Untrusted issuer', 'UntrustedIss') + } const payload = await this.verifyServiceJwt(reqCtx, { aud: this.dids.entryway ?? this.dids.pds, iss: [this.dids.admin], diff --git a/packages/pds/src/config/config.ts b/packages/pds/src/config/config.ts index 34f113df985..e433f7ed251 100644 --- a/packages/pds/src/config/config.ts +++ b/packages/pds/src/config/config.ts @@ -168,19 +168,46 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => { repoBackfillLimitMs: env.repoBackfillLimitMs ?? DAY, } - assert(env.bskyAppViewUrl) - assert(env.bskyAppViewDid) - const bskyAppViewCfg: ServerConfig['bskyAppView'] = { - url: env.bskyAppViewUrl, - did: env.bskyAppViewDid, - cdnUrlPattern: env.bskyAppViewCdnUrlPattern, + let bskyAppViewCfg: ServerConfig['bskyAppView'] = null + if (env.bskyAppViewUrl) { + assert( + env.bskyAppViewDid, + 'if bsky appview service url is configured, must configure its did as well.', + ) + bskyAppViewCfg = { + url: env.bskyAppViewUrl, + did: env.bskyAppViewDid, + cdnUrlPattern: env.bskyAppViewCdnUrlPattern, + } } - assert(env.modServiceUrl) - assert(env.modServiceDid) - const modServiceCfg: ServerConfig['modService'] = { - url: env.modServiceUrl, - did: env.modServiceDid, + let modServiceCfg: ServerConfig['modService'] = null + if (env.modServiceUrl) { + assert( + env.modServiceDid, + 'if mod service url is configured, must configure its did as well.', + ) + modServiceCfg = { + url: env.modServiceUrl, + did: env.modServiceDid, + } + } + + let reportServiceCfg: ServerConfig['reportService'] = null + if (env.reportServiceUrl) { + assert( + env.reportServiceDid, + 'if report service url is configured, must configure its did as well.', + ) + reportServiceCfg = { + url: env.reportServiceUrl, + did: env.reportServiceDid, + } + } + + // if there's a mod service, default report service into it + if (modServiceCfg && !reportServiceCfg) { + reportServiceCfg = modServiceCfg } const redisCfg: ServerConfig['redis'] = env.redisScratchAddress @@ -216,6 +243,7 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => { subscription: subscriptionCfg, bskyAppView: bskyAppViewCfg, modService: modServiceCfg, + reportService: reportServiceCfg, redis: redisCfg, rateLimits: rateLimitsCfg, crawlers: crawlersCfg, @@ -233,8 +261,9 @@ export type ServerConfig = { email: EmailConfig | null moderationEmail: EmailConfig | null subscription: SubscriptionConfig - bskyAppView: BksyAppViewConfig - modService: ModServiceConfig + bskyAppView: BksyAppViewConfig | null + modService: ModServiceConfig | null + reportService: ReportServiceConfig | null redis: RedisScratchConfig | null rateLimits: RateLimitsConfig crawlers: string[] @@ -344,3 +373,8 @@ export type ModServiceConfig = { url: string did: string } + +export type ReportServiceConfig = { + url: string + did: string +} diff --git a/packages/pds/src/config/env.ts b/packages/pds/src/config/env.ts index d096cda994e..abe1904ad5f 100644 --- a/packages/pds/src/config/env.ts +++ b/packages/pds/src/config/env.ts @@ -76,6 +76,10 @@ export const readEnv = (): ServerEnvironment => { modServiceUrl: envStr('PDS_MOD_SERVICE_URL'), modServiceDid: envStr('PDS_MOD_SERVICE_DID'), + // report service + reportServiceUrl: envStr('PDS_REPORT_SERVICE_URL'), + reportServiceDid: envStr('PDS_REPORT_SERVICE_DID'), + // rate limits rateLimitsEnabled: envBool('PDS_RATE_LIMITS_ENABLED'), rateLimitBypassKey: envStr('PDS_RATE_LIMIT_BYPASS_KEY'), @@ -176,6 +180,10 @@ export type ServerEnvironment = { modServiceUrl?: string modServiceDid?: string + // report service + reportServiceUrl?: string + reportServiceDid?: string + // rate limits rateLimitsEnabled?: boolean rateLimitBypassKey?: string diff --git a/packages/pds/src/context.ts b/packages/pds/src/context.ts index 6a5b2927df1..a9fe996056f 100644 --- a/packages/pds/src/context.ts +++ b/packages/pds/src/context.ts @@ -1,3 +1,4 @@ +import assert from 'node:assert' import * as nodemailer from 'nodemailer' import { Redis } from 'ioredis' import * as plc from '@did-plc/lib' @@ -42,8 +43,9 @@ export type AppContextOptions = { backgroundQueue: BackgroundQueue redisScratch?: Redis crawlers: Crawlers - appViewAgent: AtpAgent - moderationAgent: AtpAgent + appViewAgent?: AtpAgent + moderationAgent?: AtpAgent + reportingAgent?: AtpAgent entrywayAgent?: AtpAgent authVerifier: AuthVerifier plcRotationKey: crypto.Keypair @@ -67,8 +69,9 @@ export class AppContext { public backgroundQueue: BackgroundQueue public redisScratch?: Redis public crawlers: Crawlers - public appViewAgent: AtpAgent - public moderationAgent: AtpAgent + public appViewAgent: AtpAgent | undefined + public moderationAgent: AtpAgent | undefined + public reportingAgent: AtpAgent | undefined public entrywayAgent: AtpAgent | undefined public authVerifier: AuthVerifier public plcRotationKey: crypto.Keypair @@ -90,6 +93,7 @@ export class AppContext { this.crawlers = opts.crawlers this.appViewAgent = opts.appViewAgent this.moderationAgent = opts.moderationAgent + this.reportingAgent = opts.reportingAgent this.entrywayAgent = opts.entrywayAgent this.authVerifier = opts.authVerifier this.plcRotationKey = opts.plcRotationKey @@ -161,9 +165,15 @@ export class AppContext { ? getRedisClient(cfg.redis.address, cfg.redis.password) : undefined - const appViewAgent = new AtpAgent({ service: cfg.bskyAppView.url }) - const moderationAgent = new AtpAgent({ service: cfg.modService.url }) - + const appViewAgent = cfg.bskyAppView + ? new AtpAgent({ service: cfg.bskyAppView.url }) + : undefined + const moderationAgent = cfg.modService + ? new AtpAgent({ service: cfg.modService.url }) + : undefined + const reportingAgent = cfg.reportService + ? new AtpAgent({ service: cfg.reportService.url }) + : undefined const entrywayAgent = cfg.entryway ? new AtpAgent({ service: cfg.entryway.url }) : undefined @@ -189,7 +199,7 @@ export class AppContext { dids: { pds: cfg.service.did, entryway: cfg.entryway?.did, - admin: cfg.modService.did, + admin: cfg.modService?.did, }, }) @@ -211,8 +221,8 @@ export class AppContext { accountManager, appViewAgent, pdsHostname: cfg.service.hostname, - appviewDid: cfg.bskyAppView.did, - appviewCdnUrlPattern: cfg.bskyAppView.cdnUrlPattern, + appviewDid: cfg.bskyAppView?.did, + appviewCdnUrlPattern: cfg.bskyAppView?.cdnUrlPattern, }) return new AppContext({ @@ -231,6 +241,7 @@ export class AppContext { crawlers, appViewAgent, moderationAgent, + reportingAgent, entrywayAgent, authVerifier, plcRotationKey, @@ -240,13 +251,20 @@ export class AppContext { } async appviewAuthHeaders(did: string) { + assert(this.cfg.bskyAppView) return this.serviceAuthHeaders(did, this.cfg.bskyAppView.did) } async moderationAuthHeaders(did: string) { + assert(this.cfg.modService) return this.serviceAuthHeaders(did, this.cfg.modService.did) } + async reportingAuthHeaders(did: string) { + assert(this.cfg.reportService) + return this.serviceAuthHeaders(did, this.cfg.reportService.did) + } + async serviceAuthHeaders(did: string, aud: string) { const keypair = await this.actorStore.keypair(did) return createServiceAuthHeaders({ diff --git a/packages/pds/tests/proxied/read-after-write.test.ts b/packages/pds/tests/proxied/read-after-write.test.ts index ca01b135c5b..1bd6a463c34 100644 --- a/packages/pds/tests/proxied/read-after-write.test.ts +++ b/packages/pds/tests/proxied/read-after-write.test.ts @@ -1,4 +1,5 @@ -import util from 'util' +import util from 'node:util' +import assert from 'node:assert' import AtpAgent from '@atproto/api' import { TestNetwork, SeedClient, RecordRef } from '@atproto/dev-env' import basicSeed from '../seeds/basic' @@ -43,6 +44,7 @@ describe('proxy read after write', () => { }) it('handles image formatting', async () => { + assert(network.pds.ctx.cfg.bskyAppView) const blob = await sc.uploadFile( alice, '../dev-env/src/seed/img/key-landscape-small.jpg', @@ -123,6 +125,7 @@ describe('proxy read after write', () => { }) it('handles read after write on threads with record embeds', async () => { + assert(network.pds.ctx.cfg.bskyAppView) const img = await sc.uploadFile( alice, '../dev-env/src/seed/img/key-landscape-small.jpg', From b60719480f5f00bffd074a40e8ddc03aa93d137d Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Wed, 21 Feb 2024 19:32:09 -0600 Subject: [PATCH 024/103] Muted items prefs (#2195) * Muted items prefs * Add hidden posts * Enhance * Update to use smart objects * Add 'any' * Codegen * Enhance * Muted words methods * Dry it up * Format * Add hidden posts methods * Who codegens the codegens * Sanitize tags, always compare bare strings * Moar test * Simplify * Add test * Add changeset --- .changeset/funny-elephants-listen.md | 5 + lexicons/app/bsky/actor/defs.json | 52 +++++ packages/api/src/bsky-agent.ts | 136 +++++++++++++ packages/api/src/client/lexicons.ts | 56 ++++++ .../src/client/types/app/bsky/actor/defs.ts | 59 ++++++ packages/api/src/types.ts | 3 + packages/api/tests/bsky-agent.test.ts | 183 ++++++++++++++++++ packages/bsky/src/lexicon/lexicons.ts | 56 ++++++ .../src/lexicon/types/app/bsky/actor/defs.ts | 59 ++++++ packages/ozone/src/lexicon/lexicons.ts | 56 ++++++ .../src/lexicon/types/app/bsky/actor/defs.ts | 59 ++++++ packages/pds/src/lexicon/lexicons.ts | 56 ++++++ .../src/lexicon/types/app/bsky/actor/defs.ts | 59 ++++++ 13 files changed, 839 insertions(+) create mode 100644 .changeset/funny-elephants-listen.md diff --git a/.changeset/funny-elephants-listen.md b/.changeset/funny-elephants-listen.md new file mode 100644 index 00000000000..8135f76f989 --- /dev/null +++ b/.changeset/funny-elephants-listen.md @@ -0,0 +1,5 @@ +--- +'@atproto/api': patch +--- + +Add muted words/tags and hidden posts prefs and methods" diff --git a/lexicons/app/bsky/actor/defs.json b/lexicons/app/bsky/actor/defs.json index fa3772c4ff3..a260fc4ef5f 100644 --- a/lexicons/app/bsky/actor/defs.json +++ b/lexicons/app/bsky/actor/defs.json @@ -215,6 +215,58 @@ "description": "A list of tags which describe the account owner's interests gathered during onboarding." } } + }, + "mutedWordTarget": { + "type": "string", + "knownValues": ["content", "tag"], + "maxLength": 640, + "maxGraphemes": 64 + }, + "mutedWord": { + "type": "object", + "description": "A word that the account owner has muted.", + "required": ["value", "targets"], + "properties": { + "value": { + "type": "string", + "description": "The muted word itself.", + "maxLength": 10000, + "maxGraphemes": 1000 + }, + "targets": { + "type": "array", + "description": "The intended targets of the muted word.", + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#mutedWordTarget" + } + } + } + }, + "mutedWordsPref": { + "type": "object", + "required": ["items"], + "properties": { + "items": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#mutedWord" + }, + "description": "A list of words the account owner has muted." + } + } + }, + "hiddenPostsPref": { + "type": "object", + "required": ["items"], + "properties": { + "items": { + "type": "array", + "items": { "type": "string", "format": "at-uri" }, + "description": "A list of URIs of posts the account owner has hidden." + } + } } } } diff --git a/packages/api/src/bsky-agent.ts b/packages/api/src/bsky-agent.ts index 606e06dcda8..291cf8608c4 100644 --- a/packages/api/src/bsky-agent.ts +++ b/packages/api/src/bsky-agent.ts @@ -327,6 +327,8 @@ export class BskyAgent extends AtpAgent { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], } const res = await this.app.bsky.actor.getPreferences({}) for (const pref of res.data.preferences) { @@ -380,6 +382,20 @@ export class BskyAgent extends AtpAgent { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { $type, ...v } = pref prefs.interests = { ...prefs.interests, ...v } + } else if ( + AppBskyActorDefs.isMutedWordsPref(pref) && + AppBskyActorDefs.validateMutedWordsPref(pref).success + ) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { $type, ...v } = pref + prefs.mutedWords = v.items + } else if ( + AppBskyActorDefs.isHiddenPostsPref(pref) && + AppBskyActorDefs.validateHiddenPostsPref(pref).success + ) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { $type, ...v } = pref + prefs.hiddenPosts = v.items } } return prefs @@ -548,6 +564,26 @@ export class BskyAgent extends AtpAgent { .concat([{ ...pref, $type: 'app.bsky.actor.defs#interestsPref' }]) }) } + + async upsertMutedWords(mutedWords: AppBskyActorDefs.MutedWord[]) { + await updateMutedWords(this, mutedWords, 'upsert') + } + + async updateMutedWord(mutedWord: AppBskyActorDefs.MutedWord) { + await updateMutedWords(this, [mutedWord], 'update') + } + + async removeMutedWord(mutedWord: AppBskyActorDefs.MutedWord) { + await updateMutedWords(this, [mutedWord], 'remove') + } + + async hidePost(postUri: string) { + await updateHiddenPost(this, postUri, 'hide') + } + + async unhidePost(postUri: string) { + await updateHiddenPost(this, postUri, 'unhide') + } } /** @@ -609,3 +645,103 @@ async function updateFeedPreferences( }) return res } + +/** + * A helper specifically for updating muted words preferences + */ +async function updateMutedWords( + agent: BskyAgent, + mutedWords: AppBskyActorDefs.MutedWord[], + action: 'upsert' | 'update' | 'remove', +) { + const sanitizeMutedWord = (word: AppBskyActorDefs.MutedWord) => ({ + value: word.value.replace(/^#/, ''), + targets: word.targets, + }) + + await updatePreferences(agent, (prefs: AppBskyActorDefs.Preferences) => { + let mutedWordsPref = prefs.findLast( + (pref) => + AppBskyActorDefs.isMutedWordsPref(pref) && + AppBskyActorDefs.validateMutedWordsPref(pref).success, + ) + + if (mutedWordsPref && AppBskyActorDefs.isMutedWordsPref(mutedWordsPref)) { + if (action === 'upsert' || action === 'update') { + for (const newItem of mutedWords) { + let foundMatch = false + + for (const existingItem of mutedWordsPref.items) { + if (existingItem.value === newItem.value) { + existingItem.targets = + action === 'upsert' + ? Array.from( + new Set([...existingItem.targets, ...newItem.targets]), + ) + : newItem.targets + foundMatch = true + break + } + } + + if (action === 'upsert' && !foundMatch) { + mutedWordsPref.items.push(sanitizeMutedWord(newItem)) + } + } + } else if (action === 'remove') { + for (const word of mutedWords) { + for (let i = 0; i < mutedWordsPref.items.length; i++) { + const existing = mutedWordsPref.items[i] + if (existing.value === sanitizeMutedWord(word).value) { + mutedWordsPref.items.splice(i, 1) + break + } + } + } + } + } else { + // if the pref doesn't exist, create it + if (action === 'upsert') { + mutedWordsPref = { + items: mutedWords.map(sanitizeMutedWord), + } + } + } + + return prefs + .filter((p) => !AppBskyActorDefs.isMutedWordsPref(p)) + .concat([ + { ...mutedWordsPref, $type: 'app.bsky.actor.defs#mutedWordsPref' }, + ]) + }) +} + +async function updateHiddenPost( + agent: BskyAgent, + postUri: string, + action: 'hide' | 'unhide', +) { + await updatePreferences(agent, (prefs: AppBskyActorDefs.Preferences) => { + let pref = prefs.findLast( + (pref) => + AppBskyActorDefs.isHiddenPostsPref(pref) && + AppBskyActorDefs.validateHiddenPostsPref(pref).success, + ) + if (pref && AppBskyActorDefs.isHiddenPostsPref(pref)) { + pref.items = + action === 'hide' + ? Array.from(new Set([...pref.items, postUri])) + : pref.items.filter((uri) => uri !== postUri) + } else { + if (action === 'hide') { + pref = { + $type: 'app.bsky.actor.defs#hiddenPostsPref', + items: [postUri], + } + } + } + return prefs + .filter((p) => !AppBskyActorDefs.isInterestsPref(p)) + .concat([{ ...pref, $type: 'app.bsky.actor.defs#hiddenPostsPref' }]) + }) +} diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index d873a876865..88c27c79b18 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -5198,6 +5198,62 @@ export const schemaDict = { }, }, }, + mutedWordTarget: { + type: 'string', + knownValues: ['content', 'tag'], + maxLength: 640, + maxGraphemes: 64, + }, + mutedWord: { + type: 'object', + description: 'A word that the account owner has muted.', + required: ['value', 'targets'], + properties: { + value: { + type: 'string', + description: 'The muted word itself.', + maxLength: 10000, + maxGraphemes: 1000, + }, + targets: { + type: 'array', + description: 'The intended targets of the muted word.', + items: { + type: 'ref', + ref: 'lex:app.bsky.actor.defs#mutedWordTarget', + }, + }, + }, + }, + mutedWordsPref: { + type: 'object', + required: ['items'], + properties: { + items: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.actor.defs#mutedWord', + }, + description: 'A list of words the account owner has muted.', + }, + }, + }, + hiddenPostsPref: { + type: 'object', + required: ['items'], + properties: { + items: { + type: 'array', + items: { + type: 'string', + format: 'at-uri', + }, + description: + 'A list of URIs of posts the account owner has hidden.', + }, + }, + }, }, }, AppBskyActorGetPreferences: { diff --git a/packages/api/src/client/types/app/bsky/actor/defs.ts b/packages/api/src/client/types/app/bsky/actor/defs.ts index 5f493576334..535cfe9094f 100644 --- a/packages/api/src/client/types/app/bsky/actor/defs.ts +++ b/packages/api/src/client/types/app/bsky/actor/defs.ts @@ -254,3 +254,62 @@ export function isInterestsPref(v: unknown): v is InterestsPref { export function validateInterestsPref(v: unknown): ValidationResult { return lexicons.validate('app.bsky.actor.defs#interestsPref', v) } + +export type MutedWordTarget = 'content' | 'tag' | (string & {}) + +/** A word that the account owner has muted. */ +export interface MutedWord { + /** The muted word itself. */ + value: string + /** The intended targets of the muted word. */ + targets: MutedWordTarget[] + [k: string]: unknown +} + +export function isMutedWord(v: unknown): v is MutedWord { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.actor.defs#mutedWord' + ) +} + +export function validateMutedWord(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.actor.defs#mutedWord', v) +} + +export interface MutedWordsPref { + /** A list of words the account owner has muted. */ + items: MutedWord[] + [k: string]: unknown +} + +export function isMutedWordsPref(v: unknown): v is MutedWordsPref { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.actor.defs#mutedWordsPref' + ) +} + +export function validateMutedWordsPref(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.actor.defs#mutedWordsPref', v) +} + +export interface HiddenPostsPref { + /** A list of URIs of posts the account owner has hidden. */ + items: string[] + [k: string]: unknown +} + +export function isHiddenPostsPref(v: unknown): v is HiddenPostsPref { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.actor.defs#hiddenPostsPref' + ) +} + +export function validateHiddenPostsPref(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.actor.defs#hiddenPostsPref', v) +} diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts index 3d6f73baa33..7e36e77de58 100644 --- a/packages/api/src/types.ts +++ b/packages/api/src/types.ts @@ -1,3 +1,4 @@ +import { AppBskyActorNS, AppBskyActorDefs } from './client' import { LabelPreference } from './moderation/types' /** @@ -119,4 +120,6 @@ export interface BskyPreferences { contentLabels: Record birthDate: Date | undefined interests: BskyInterestsPreference + mutedWords: AppBskyActorDefs.MutedWord[] + hiddenPosts: string[] } diff --git a/packages/api/tests/bsky-agent.test.ts b/packages/api/tests/bsky-agent.test.ts index 5f850b19e91..edaf3912ec7 100644 --- a/packages/api/tests/bsky-agent.test.ts +++ b/packages/api/tests/bsky-agent.test.ts @@ -239,6 +239,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.setAdultContentEnabled(true) @@ -263,6 +265,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.setAdultContentEnabled(false) @@ -287,6 +291,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.setContentLabelPref('impersonation', 'warn') @@ -313,6 +319,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.setContentLabelPref('spam', 'show') // will convert to 'ignore' @@ -341,6 +349,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.addSavedFeed('at://bob.com/app.bsky.feed.generator/fake') @@ -371,6 +381,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.addPinnedFeed('at://bob.com/app.bsky.feed.generator/fake') @@ -401,6 +413,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.removePinnedFeed('at://bob.com/app.bsky.feed.generator/fake') @@ -431,6 +445,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.removeSavedFeed('at://bob.com/app.bsky.feed.generator/fake') @@ -461,6 +477,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.addPinnedFeed('at://bob.com/app.bsky.feed.generator/fake') @@ -491,6 +509,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.addPinnedFeed('at://bob.com/app.bsky.feed.generator/fake2') @@ -527,6 +547,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.removeSavedFeed('at://bob.com/app.bsky.feed.generator/fake') @@ -557,6 +579,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.setPersonalDetails({ birthDate: '2023-09-11T18:05:42.556Z' }) @@ -587,6 +611,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.setFeedViewPrefs('home', { hideReplies: true }) @@ -617,6 +643,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.setFeedViewPrefs('home', { hideReplies: false }) @@ -647,6 +675,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.setFeedViewPrefs('other', { hideReplies: true }) @@ -684,6 +714,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.setThreadViewPrefs({ sort: 'random' }) @@ -721,6 +753,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.setThreadViewPrefs({ sort: 'oldest' }) @@ -758,6 +792,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.setInterestsPref({ tags: ['foo', 'bar'] }) @@ -795,6 +831,8 @@ describe('agent', () => { interests: { tags: ['foo', 'bar'], }, + mutedWords: [], + hiddenPosts: [], }) }) @@ -921,6 +959,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.setAdultContentEnabled(false) @@ -950,6 +990,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.setContentLabelPref('nsfw', 'hide') @@ -979,6 +1021,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.addPinnedFeed('at://bob.com/app.bsky.feed.generator/fake') @@ -1008,6 +1052,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.setPersonalDetails({ birthDate: '2023-09-11T18:05:42.556Z' }) @@ -1037,6 +1083,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) await agent.setFeedViewPrefs('home', { @@ -1077,6 +1125,8 @@ describe('agent', () => { interests: { tags: [], }, + mutedWords: [], + hiddenPosts: [], }) const res = await agent.app.bsky.actor.getPreferences() @@ -1118,6 +1168,139 @@ describe('agent', () => { ].sort(byType), ) }) + + describe('muted words', () => { + let agent: BskyAgent + const mutedWords = [ + { value: 'both', targets: ['content', 'tag'] }, + { value: 'content', targets: ['content'] }, + { value: 'tag', targets: ['tag'] }, + { value: 'tag_then_both', targets: ['tag'] }, + { value: 'tag_then_content', targets: ['tag'] }, + { value: 'tag_then_none', targets: ['tag'] }, + ] + + beforeAll(async () => { + agent = new BskyAgent({ service: network.pds.url }) + await agent.createAccount({ + handle: 'user7.test', + email: 'user7@test.com', + password: 'password', + }) + }) + + it('upsertMutedWords', async () => { + await agent.upsertMutedWords(mutedWords) + await agent.upsertMutedWords(mutedWords) // double + await expect(agent.getPreferences()).resolves.toHaveProperty( + 'mutedWords', + mutedWords, + ) + }) + + it('upsertMutedWords with #', async () => { + await agent.upsertMutedWords([{ value: '#hashtag', targets: ['tag'] }]) + const { mutedWords } = await agent.getPreferences() + expect(mutedWords.find((m) => m.value === '#hashtag')).toBeFalsy() + expect(mutedWords.find((m) => m.value === 'hashtag')).toBeTruthy() + }) + + it('updateMutedWord', async () => { + await agent.updateMutedWord({ + value: 'tag_then_content', + targets: ['content'], + }) + await agent.updateMutedWord({ + value: 'tag_then_both', + targets: ['content', 'tag'], + }) + await agent.updateMutedWord({ value: 'tag_then_none', targets: [] }) + await agent.updateMutedWord({ value: 'no_exist', targets: ['tag'] }) + const { mutedWords } = await agent.getPreferences() + + expect( + mutedWords.find((m) => m.value === 'tag_then_content'), + ).toHaveProperty('targets', ['content']) + expect( + mutedWords.find((m) => m.value === 'tag_then_both'), + ).toHaveProperty('targets', ['content', 'tag']) + expect( + mutedWords.find((m) => m.value === 'tag_then_none'), + ).toHaveProperty('targets', []) + expect(mutedWords.find((m) => m.value === 'no_exist')).toBeFalsy() + }) + + it('updateMutedWord with #', async () => { + await agent.updateMutedWord({ + value: 'hashtag', + targets: ['tag', 'content'], + }) + const { mutedWords } = await agent.getPreferences() + expect(mutedWords.find((m) => m.value === 'hashtag')).toStrictEqual({ + value: 'hashtag', + targets: ['tag', 'content'], + }) + }) + + it('removeMutedWord', async () => { + await agent.removeMutedWord({ value: 'tag_then_content', targets: [] }) + await agent.removeMutedWord({ value: 'tag_then_both', targets: [] }) + await agent.removeMutedWord({ value: 'tag_then_none', targets: [] }) + const { mutedWords } = await agent.getPreferences() + + expect( + mutedWords.find((m) => m.value === 'tag_then_content'), + ).toBeFalsy() + expect(mutedWords.find((m) => m.value === 'tag_then_both')).toBeFalsy() + expect(mutedWords.find((m) => m.value === 'tag_then_none')).toBeFalsy() + }) + + it('removeMutedWord with #', async () => { + await agent.removeMutedWord({ value: '#hashtag', targets: [] }) + const { mutedWords } = await agent.getPreferences() + + expect(mutedWords.find((m) => m.value === 'hashtag')).toBeFalsy() + }) + }) + + describe('hidden posts', () => { + let agent: BskyAgent + const postUri = 'at://did:plc:fake/app.bsky.feed.post/fake' + + beforeAll(async () => { + agent = new BskyAgent({ service: network.pds.url }) + await agent.createAccount({ + handle: 'user8.test', + email: 'user8@test.com', + password: 'password', + }) + }) + + it('hidePost', async () => { + await agent.hidePost(postUri) + await agent.hidePost(postUri) // double, should dedupe + await expect(agent.getPreferences()).resolves.toHaveProperty( + 'hiddenPosts', + [postUri], + ) + }) + + it('unhidePost', async () => { + await agent.unhidePost(postUri) + await expect(agent.getPreferences()).resolves.toHaveProperty( + 'hiddenPosts', + [], + ) + // no issues calling a second time + await agent.unhidePost(postUri) + await expect(agent.getPreferences()).resolves.toHaveProperty( + 'hiddenPosts', + [], + ) + }) + }) + + // end }) }) diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index d873a876865..88c27c79b18 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -5198,6 +5198,62 @@ export const schemaDict = { }, }, }, + mutedWordTarget: { + type: 'string', + knownValues: ['content', 'tag'], + maxLength: 640, + maxGraphemes: 64, + }, + mutedWord: { + type: 'object', + description: 'A word that the account owner has muted.', + required: ['value', 'targets'], + properties: { + value: { + type: 'string', + description: 'The muted word itself.', + maxLength: 10000, + maxGraphemes: 1000, + }, + targets: { + type: 'array', + description: 'The intended targets of the muted word.', + items: { + type: 'ref', + ref: 'lex:app.bsky.actor.defs#mutedWordTarget', + }, + }, + }, + }, + mutedWordsPref: { + type: 'object', + required: ['items'], + properties: { + items: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.actor.defs#mutedWord', + }, + description: 'A list of words the account owner has muted.', + }, + }, + }, + hiddenPostsPref: { + type: 'object', + required: ['items'], + properties: { + items: { + type: 'array', + items: { + type: 'string', + format: 'at-uri', + }, + description: + 'A list of URIs of posts the account owner has hidden.', + }, + }, + }, }, }, AppBskyActorGetPreferences: { diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts index a802f70f44b..50afff62a0f 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts @@ -254,3 +254,62 @@ export function isInterestsPref(v: unknown): v is InterestsPref { export function validateInterestsPref(v: unknown): ValidationResult { return lexicons.validate('app.bsky.actor.defs#interestsPref', v) } + +export type MutedWordTarget = 'content' | 'tag' | (string & {}) + +/** A word that the account owner has muted. */ +export interface MutedWord { + /** The muted word itself. */ + value: string + /** The intended targets of the muted word. */ + targets: MutedWordTarget[] + [k: string]: unknown +} + +export function isMutedWord(v: unknown): v is MutedWord { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.actor.defs#mutedWord' + ) +} + +export function validateMutedWord(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.actor.defs#mutedWord', v) +} + +export interface MutedWordsPref { + /** A list of words the account owner has muted. */ + items: MutedWord[] + [k: string]: unknown +} + +export function isMutedWordsPref(v: unknown): v is MutedWordsPref { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.actor.defs#mutedWordsPref' + ) +} + +export function validateMutedWordsPref(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.actor.defs#mutedWordsPref', v) +} + +export interface HiddenPostsPref { + /** A list of URIs of posts the account owner has hidden. */ + items: string[] + [k: string]: unknown +} + +export function isHiddenPostsPref(v: unknown): v is HiddenPostsPref { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.actor.defs#hiddenPostsPref' + ) +} + +export function validateHiddenPostsPref(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.actor.defs#hiddenPostsPref', v) +} diff --git a/packages/ozone/src/lexicon/lexicons.ts b/packages/ozone/src/lexicon/lexicons.ts index d873a876865..88c27c79b18 100644 --- a/packages/ozone/src/lexicon/lexicons.ts +++ b/packages/ozone/src/lexicon/lexicons.ts @@ -5198,6 +5198,62 @@ export const schemaDict = { }, }, }, + mutedWordTarget: { + type: 'string', + knownValues: ['content', 'tag'], + maxLength: 640, + maxGraphemes: 64, + }, + mutedWord: { + type: 'object', + description: 'A word that the account owner has muted.', + required: ['value', 'targets'], + properties: { + value: { + type: 'string', + description: 'The muted word itself.', + maxLength: 10000, + maxGraphemes: 1000, + }, + targets: { + type: 'array', + description: 'The intended targets of the muted word.', + items: { + type: 'ref', + ref: 'lex:app.bsky.actor.defs#mutedWordTarget', + }, + }, + }, + }, + mutedWordsPref: { + type: 'object', + required: ['items'], + properties: { + items: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.actor.defs#mutedWord', + }, + description: 'A list of words the account owner has muted.', + }, + }, + }, + hiddenPostsPref: { + type: 'object', + required: ['items'], + properties: { + items: { + type: 'array', + items: { + type: 'string', + format: 'at-uri', + }, + description: + 'A list of URIs of posts the account owner has hidden.', + }, + }, + }, }, }, AppBskyActorGetPreferences: { diff --git a/packages/ozone/src/lexicon/types/app/bsky/actor/defs.ts b/packages/ozone/src/lexicon/types/app/bsky/actor/defs.ts index a802f70f44b..50afff62a0f 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/actor/defs.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/actor/defs.ts @@ -254,3 +254,62 @@ export function isInterestsPref(v: unknown): v is InterestsPref { export function validateInterestsPref(v: unknown): ValidationResult { return lexicons.validate('app.bsky.actor.defs#interestsPref', v) } + +export type MutedWordTarget = 'content' | 'tag' | (string & {}) + +/** A word that the account owner has muted. */ +export interface MutedWord { + /** The muted word itself. */ + value: string + /** The intended targets of the muted word. */ + targets: MutedWordTarget[] + [k: string]: unknown +} + +export function isMutedWord(v: unknown): v is MutedWord { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.actor.defs#mutedWord' + ) +} + +export function validateMutedWord(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.actor.defs#mutedWord', v) +} + +export interface MutedWordsPref { + /** A list of words the account owner has muted. */ + items: MutedWord[] + [k: string]: unknown +} + +export function isMutedWordsPref(v: unknown): v is MutedWordsPref { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.actor.defs#mutedWordsPref' + ) +} + +export function validateMutedWordsPref(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.actor.defs#mutedWordsPref', v) +} + +export interface HiddenPostsPref { + /** A list of URIs of posts the account owner has hidden. */ + items: string[] + [k: string]: unknown +} + +export function isHiddenPostsPref(v: unknown): v is HiddenPostsPref { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.actor.defs#hiddenPostsPref' + ) +} + +export function validateHiddenPostsPref(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.actor.defs#hiddenPostsPref', v) +} diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index d873a876865..88c27c79b18 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -5198,6 +5198,62 @@ export const schemaDict = { }, }, }, + mutedWordTarget: { + type: 'string', + knownValues: ['content', 'tag'], + maxLength: 640, + maxGraphemes: 64, + }, + mutedWord: { + type: 'object', + description: 'A word that the account owner has muted.', + required: ['value', 'targets'], + properties: { + value: { + type: 'string', + description: 'The muted word itself.', + maxLength: 10000, + maxGraphemes: 1000, + }, + targets: { + type: 'array', + description: 'The intended targets of the muted word.', + items: { + type: 'ref', + ref: 'lex:app.bsky.actor.defs#mutedWordTarget', + }, + }, + }, + }, + mutedWordsPref: { + type: 'object', + required: ['items'], + properties: { + items: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.actor.defs#mutedWord', + }, + description: 'A list of words the account owner has muted.', + }, + }, + }, + hiddenPostsPref: { + type: 'object', + required: ['items'], + properties: { + items: { + type: 'array', + items: { + type: 'string', + format: 'at-uri', + }, + description: + 'A list of URIs of posts the account owner has hidden.', + }, + }, + }, }, }, AppBskyActorGetPreferences: { diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts b/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts index a802f70f44b..50afff62a0f 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts @@ -254,3 +254,62 @@ export function isInterestsPref(v: unknown): v is InterestsPref { export function validateInterestsPref(v: unknown): ValidationResult { return lexicons.validate('app.bsky.actor.defs#interestsPref', v) } + +export type MutedWordTarget = 'content' | 'tag' | (string & {}) + +/** A word that the account owner has muted. */ +export interface MutedWord { + /** The muted word itself. */ + value: string + /** The intended targets of the muted word. */ + targets: MutedWordTarget[] + [k: string]: unknown +} + +export function isMutedWord(v: unknown): v is MutedWord { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.actor.defs#mutedWord' + ) +} + +export function validateMutedWord(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.actor.defs#mutedWord', v) +} + +export interface MutedWordsPref { + /** A list of words the account owner has muted. */ + items: MutedWord[] + [k: string]: unknown +} + +export function isMutedWordsPref(v: unknown): v is MutedWordsPref { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.actor.defs#mutedWordsPref' + ) +} + +export function validateMutedWordsPref(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.actor.defs#mutedWordsPref', v) +} + +export interface HiddenPostsPref { + /** A list of URIs of posts the account owner has hidden. */ + items: string[] + [k: string]: unknown +} + +export function isHiddenPostsPref(v: unknown): v is HiddenPostsPref { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.actor.defs#hiddenPostsPref' + ) +} + +export function validateHiddenPostsPref(v: unknown): ValidationResult { + return lexicons.validate('app.bsky.actor.defs#hiddenPostsPref', v) +} From 6d30e2c144b051f25ea23c8f0143686292361803 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 19:38:02 -0600 Subject: [PATCH 025/103] Version packages (#2202) Co-authored-by: github-actions[bot] --- .changeset/funny-elephants-listen.md | 5 ----- .changeset/slimy-games-breathe.md | 5 ----- packages/api/CHANGELOG.md | 10 ++++++++++ packages/api/package.json | 2 +- packages/bsky/CHANGELOG.md | 7 +++++++ packages/bsky/package.json | 2 +- packages/dev-env/CHANGELOG.md | 10 ++++++++++ packages/dev-env/package.json | 2 +- packages/ozone/CHANGELOG.md | 7 +++++++ packages/ozone/package.json | 2 +- packages/pds/CHANGELOG.md | 7 +++++++ packages/pds/package.json | 2 +- 12 files changed, 46 insertions(+), 15 deletions(-) delete mode 100644 .changeset/funny-elephants-listen.md delete mode 100644 .changeset/slimy-games-breathe.md diff --git a/.changeset/funny-elephants-listen.md b/.changeset/funny-elephants-listen.md deleted file mode 100644 index 8135f76f989..00000000000 --- a/.changeset/funny-elephants-listen.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@atproto/api': patch ---- - -Add muted words/tags and hidden posts prefs and methods" diff --git a/.changeset/slimy-games-breathe.md b/.changeset/slimy-games-breathe.md deleted file mode 100644 index 7551f0cb235..00000000000 --- a/.changeset/slimy-games-breathe.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@atproto/api': minor ---- - -Add lexicons and methods for account migration diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md index f2117363038..64d884e0969 100644 --- a/packages/api/CHANGELOG.md +++ b/packages/api/CHANGELOG.md @@ -1,5 +1,15 @@ # @atproto/api +## 0.10.0 + +### Minor Changes + +- [#2170](https://github.com/bluesky-social/atproto/pull/2170) [`4c511b3d9`](https://github.com/bluesky-social/atproto/commit/4c511b3d9de41ffeae3fc11db941e7df04f4468a) Thanks [@dholms](https://github.com/dholms)! - Add lexicons and methods for account migration + +### Patch Changes + +- [#2195](https://github.com/bluesky-social/atproto/pull/2195) [`b60719480`](https://github.com/bluesky-social/atproto/commit/b60719480f5f00bffd074a40e8ddc03aa93d137d) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Add muted words/tags and hidden posts prefs and methods" + ## 0.9.8 ### Patch Changes diff --git a/packages/api/package.json b/packages/api/package.json index 4d803f3b83a..c49ef290d46 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/api", - "version": "0.9.8", + "version": "0.10.0", "license": "MIT", "description": "Client library for atproto and Bluesky", "keywords": [ diff --git a/packages/bsky/CHANGELOG.md b/packages/bsky/CHANGELOG.md index ea961d46b57..b3181272bee 100644 --- a/packages/bsky/CHANGELOG.md +++ b/packages/bsky/CHANGELOG.md @@ -1,5 +1,12 @@ # @atproto/bsky +## 0.0.32 + +### Patch Changes + +- Updated dependencies [[`b60719480`](https://github.com/bluesky-social/atproto/commit/b60719480f5f00bffd074a40e8ddc03aa93d137d), [`4c511b3d9`](https://github.com/bluesky-social/atproto/commit/4c511b3d9de41ffeae3fc11db941e7df04f4468a)]: + - @atproto/api@0.10.0 + ## 0.0.31 ### Patch Changes diff --git a/packages/bsky/package.json b/packages/bsky/package.json index 2a7a78dc442..3402fd187b2 100644 --- a/packages/bsky/package.json +++ b/packages/bsky/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/bsky", - "version": "0.0.31", + "version": "0.0.32", "license": "MIT", "description": "Reference implementation of app.bsky App View (Bluesky API)", "keywords": [ diff --git a/packages/dev-env/CHANGELOG.md b/packages/dev-env/CHANGELOG.md index 3dadfec79b3..41f20d87af3 100644 --- a/packages/dev-env/CHANGELOG.md +++ b/packages/dev-env/CHANGELOG.md @@ -1,5 +1,15 @@ # @atproto/dev-env +## 0.2.32 + +### Patch Changes + +- Updated dependencies [[`b60719480`](https://github.com/bluesky-social/atproto/commit/b60719480f5f00bffd074a40e8ddc03aa93d137d), [`4c511b3d9`](https://github.com/bluesky-social/atproto/commit/4c511b3d9de41ffeae3fc11db941e7df04f4468a)]: + - @atproto/api@0.10.0 + - @atproto/bsky@0.0.32 + - @atproto/ozone@0.0.11 + - @atproto/pds@0.4.0 + ## 0.2.31 ### Patch Changes diff --git a/packages/dev-env/package.json b/packages/dev-env/package.json index a0cf959d3ee..7e99e225645 100644 --- a/packages/dev-env/package.json +++ b/packages/dev-env/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/dev-env", - "version": "0.2.31", + "version": "0.2.32", "license": "MIT", "description": "Local development environment helper for atproto development", "keywords": [ diff --git a/packages/ozone/CHANGELOG.md b/packages/ozone/CHANGELOG.md index 5d484430cd9..dd88ccf6fc8 100644 --- a/packages/ozone/CHANGELOG.md +++ b/packages/ozone/CHANGELOG.md @@ -1,5 +1,12 @@ # @atproto/ozone +## 0.0.11 + +### Patch Changes + +- Updated dependencies [[`b60719480`](https://github.com/bluesky-social/atproto/commit/b60719480f5f00bffd074a40e8ddc03aa93d137d), [`4c511b3d9`](https://github.com/bluesky-social/atproto/commit/4c511b3d9de41ffeae3fc11db941e7df04f4468a)]: + - @atproto/api@0.10.0 + ## 0.0.10 ### Patch Changes diff --git a/packages/ozone/package.json b/packages/ozone/package.json index 875118e2a56..0f160259644 100644 --- a/packages/ozone/package.json +++ b/packages/ozone/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/ozone", - "version": "0.0.10", + "version": "0.0.11", "license": "MIT", "description": "Backend service for moderating the Bluesky network.", "keywords": [ diff --git a/packages/pds/CHANGELOG.md b/packages/pds/CHANGELOG.md index 82df25d1448..c131d37fa01 100644 --- a/packages/pds/CHANGELOG.md +++ b/packages/pds/CHANGELOG.md @@ -1,5 +1,12 @@ # @atproto/pds +## 0.4.0 + +### Patch Changes + +- Updated dependencies [[`b60719480`](https://github.com/bluesky-social/atproto/commit/b60719480f5f00bffd074a40e8ddc03aa93d137d), [`4c511b3d9`](https://github.com/bluesky-social/atproto/commit/4c511b3d9de41ffeae3fc11db941e7df04f4468a)]: + - @atproto/api@0.10.0 + ## 0.3.19 ### Patch Changes diff --git a/packages/pds/package.json b/packages/pds/package.json index 4140b575645..c620132ed25 100644 --- a/packages/pds/package.json +++ b/packages/pds/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/pds", - "version": "0.4.0-beta", + "version": "0.4.0", "license": "MIT", "description": "Reference implementation of atproto Personal Data Server (PDS)", "keywords": [ From 87d59ff541aba944bcc052c098653ba60e7add46 Mon Sep 17 00:00:00 2001 From: Daniel Holmgren Date: Wed, 21 Feb 2024 23:58:25 -0600 Subject: [PATCH 026/103] Admin: updateAccountPassword (#2212) * add update account password route * add test --- .../atproto/admin/updateAccountPassword.json | 21 ++++++++++ packages/api/src/client/index.ts | 13 ++++++ packages/api/src/client/lexicons.ts | 29 +++++++++++++ .../atproto/admin/updateAccountPassword.ts | 33 +++++++++++++++ packages/bsky/src/lexicon/index.ts | 12 ++++++ packages/bsky/src/lexicon/lexicons.ts | 29 +++++++++++++ .../atproto/admin/updateAccountPassword.ts | 39 +++++++++++++++++ packages/ozone/src/lexicon/index.ts | 12 ++++++ packages/ozone/src/lexicon/lexicons.ts | 29 +++++++++++++ .../atproto/admin/updateAccountPassword.ts | 39 +++++++++++++++++ packages/pds/src/account-manager/index.ts | 5 +++ .../pds/src/api/com/atproto/admin/index.ts | 2 + .../atproto/admin/updateAccountPassword.ts | 28 +++++++++++++ packages/pds/src/lexicon/index.ts | 12 ++++++ packages/pds/src/lexicon/lexicons.ts | 29 +++++++++++++ .../atproto/admin/updateAccountPassword.ts | 39 +++++++++++++++++ packages/pds/tests/account.test.ts | 42 +++++++++++++++++++ 17 files changed, 413 insertions(+) create mode 100644 lexicons/com/atproto/admin/updateAccountPassword.json create mode 100644 packages/api/src/client/types/com/atproto/admin/updateAccountPassword.ts create mode 100644 packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountPassword.ts create mode 100644 packages/ozone/src/lexicon/types/com/atproto/admin/updateAccountPassword.ts create mode 100644 packages/pds/src/api/com/atproto/admin/updateAccountPassword.ts create mode 100644 packages/pds/src/lexicon/types/com/atproto/admin/updateAccountPassword.ts diff --git a/lexicons/com/atproto/admin/updateAccountPassword.json b/lexicons/com/atproto/admin/updateAccountPassword.json new file mode 100644 index 00000000000..76c69fec0b6 --- /dev/null +++ b/lexicons/com/atproto/admin/updateAccountPassword.json @@ -0,0 +1,21 @@ +{ + "lexicon": 1, + "id": "com.atproto.admin.updateAccountPassword", + "defs": { + "main": { + "type": "procedure", + "description": "Update the password for a user account as an administrator.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["did", "password"], + "properties": { + "did": { "type": "string", "format": "did" }, + "password": { "type": "string" } + } + } + } + } + } +} diff --git a/packages/api/src/client/index.ts b/packages/api/src/client/index.ts index 5029db88701..846c379b7a8 100644 --- a/packages/api/src/client/index.ts +++ b/packages/api/src/client/index.ts @@ -29,6 +29,7 @@ import * as ComAtprotoAdminSearchRepos from './types/com/atproto/admin/searchRep import * as ComAtprotoAdminSendEmail from './types/com/atproto/admin/sendEmail' import * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/updateAccountEmail' import * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle' +import * as ComAtprotoAdminUpdateAccountPassword from './types/com/atproto/admin/updateAccountPassword' import * as ComAtprotoAdminUpdateCommunicationTemplate from './types/com/atproto/admin/updateCommunicationTemplate' import * as ComAtprotoAdminUpdateSubjectStatus from './types/com/atproto/admin/updateSubjectStatus' import * as ComAtprotoIdentityGetRecommendedDidCredentials from './types/com/atproto/identity/getRecommendedDidCredentials' @@ -182,6 +183,7 @@ export * as ComAtprotoAdminSearchRepos from './types/com/atproto/admin/searchRep export * as ComAtprotoAdminSendEmail from './types/com/atproto/admin/sendEmail' export * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/updateAccountEmail' export * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle' +export * as ComAtprotoAdminUpdateAccountPassword from './types/com/atproto/admin/updateAccountPassword' export * as ComAtprotoAdminUpdateCommunicationTemplate from './types/com/atproto/admin/updateCommunicationTemplate' export * as ComAtprotoAdminUpdateSubjectStatus from './types/com/atproto/admin/updateSubjectStatus' export * as ComAtprotoIdentityGetRecommendedDidCredentials from './types/com/atproto/identity/getRecommendedDidCredentials' @@ -654,6 +656,17 @@ export class ComAtprotoAdminNS { }) } + updateAccountPassword( + data?: ComAtprotoAdminUpdateAccountPassword.InputSchema, + opts?: ComAtprotoAdminUpdateAccountPassword.CallOptions, + ): Promise { + return this._service.xrpc + .call('com.atproto.admin.updateAccountPassword', opts?.qp, data, opts) + .catch((e) => { + throw ComAtprotoAdminUpdateAccountPassword.toKnownErr(e) + }) + } + updateCommunicationTemplate( data?: ComAtprotoAdminUpdateCommunicationTemplate.InputSchema, opts?: ComAtprotoAdminUpdateCommunicationTemplate.CallOptions, diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index 88c27c79b18..77bedd176bb 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -1863,6 +1863,33 @@ export const schemaDict = { }, }, }, + ComAtprotoAdminUpdateAccountPassword: { + lexicon: 1, + id: 'com.atproto.admin.updateAccountPassword', + defs: { + main: { + type: 'procedure', + description: + 'Update the password for a user account as an administrator.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['did', 'password'], + properties: { + did: { + type: 'string', + format: 'did', + }, + password: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, ComAtprotoAdminUpdateCommunicationTemplate: { lexicon: 1, id: 'com.atproto.admin.updateCommunicationTemplate', @@ -8860,6 +8887,8 @@ export const ids = { ComAtprotoAdminSendEmail: 'com.atproto.admin.sendEmail', ComAtprotoAdminUpdateAccountEmail: 'com.atproto.admin.updateAccountEmail', ComAtprotoAdminUpdateAccountHandle: 'com.atproto.admin.updateAccountHandle', + ComAtprotoAdminUpdateAccountPassword: + 'com.atproto.admin.updateAccountPassword', ComAtprotoAdminUpdateCommunicationTemplate: 'com.atproto.admin.updateCommunicationTemplate', ComAtprotoAdminUpdateSubjectStatus: 'com.atproto.admin.updateSubjectStatus', diff --git a/packages/api/src/client/types/com/atproto/admin/updateAccountPassword.ts b/packages/api/src/client/types/com/atproto/admin/updateAccountPassword.ts new file mode 100644 index 00000000000..99ef3881c37 --- /dev/null +++ b/packages/api/src/client/types/com/atproto/admin/updateAccountPassword.ts @@ -0,0 +1,33 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import { Headers, XRPCError } from '@atproto/xrpc' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { isObj, hasProp } from '../../../../util' +import { lexicons } from '../../../../lexicons' +import { CID } from 'multiformats/cid' + +export interface QueryParams {} + +export interface InputSchema { + did: string + password: string + [k: string]: unknown +} + +export interface CallOptions { + headers?: Headers + qp?: QueryParams + encoding: 'application/json' +} + +export interface Response { + success: boolean + headers: Headers +} + +export function toKnownErr(e: any) { + if (e instanceof XRPCError) { + } + return e +} diff --git a/packages/bsky/src/lexicon/index.ts b/packages/bsky/src/lexicon/index.ts index 6b3b6de582a..cf2c613e686 100644 --- a/packages/bsky/src/lexicon/index.ts +++ b/packages/bsky/src/lexicon/index.ts @@ -30,6 +30,7 @@ import * as ComAtprotoAdminSearchRepos from './types/com/atproto/admin/searchRep import * as ComAtprotoAdminSendEmail from './types/com/atproto/admin/sendEmail' import * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/updateAccountEmail' import * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle' +import * as ComAtprotoAdminUpdateAccountPassword from './types/com/atproto/admin/updateAccountPassword' import * as ComAtprotoAdminUpdateCommunicationTemplate from './types/com/atproto/admin/updateCommunicationTemplate' import * as ComAtprotoAdminUpdateSubjectStatus from './types/com/atproto/admin/updateSubjectStatus' import * as ComAtprotoIdentityGetRecommendedDidCredentials from './types/com/atproto/identity/getRecommendedDidCredentials' @@ -444,6 +445,17 @@ export class ComAtprotoAdminNS { return this._server.xrpc.method(nsid, cfg) } + updateAccountPassword( + cfg: ConfigOf< + AV, + ComAtprotoAdminUpdateAccountPassword.Handler>, + ComAtprotoAdminUpdateAccountPassword.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.admin.updateAccountPassword' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + updateCommunicationTemplate( cfg: ConfigOf< AV, diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index 88c27c79b18..77bedd176bb 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -1863,6 +1863,33 @@ export const schemaDict = { }, }, }, + ComAtprotoAdminUpdateAccountPassword: { + lexicon: 1, + id: 'com.atproto.admin.updateAccountPassword', + defs: { + main: { + type: 'procedure', + description: + 'Update the password for a user account as an administrator.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['did', 'password'], + properties: { + did: { + type: 'string', + format: 'did', + }, + password: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, ComAtprotoAdminUpdateCommunicationTemplate: { lexicon: 1, id: 'com.atproto.admin.updateCommunicationTemplate', @@ -8860,6 +8887,8 @@ export const ids = { ComAtprotoAdminSendEmail: 'com.atproto.admin.sendEmail', ComAtprotoAdminUpdateAccountEmail: 'com.atproto.admin.updateAccountEmail', ComAtprotoAdminUpdateAccountHandle: 'com.atproto.admin.updateAccountHandle', + ComAtprotoAdminUpdateAccountPassword: + 'com.atproto.admin.updateAccountPassword', ComAtprotoAdminUpdateCommunicationTemplate: 'com.atproto.admin.updateCommunicationTemplate', ComAtprotoAdminUpdateSubjectStatus: 'com.atproto.admin.updateSubjectStatus', diff --git a/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountPassword.ts b/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountPassword.ts new file mode 100644 index 00000000000..948568f0d3d --- /dev/null +++ b/packages/bsky/src/lexicon/types/com/atproto/admin/updateAccountPassword.ts @@ -0,0 +1,39 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + did: string + password: string + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/ozone/src/lexicon/index.ts b/packages/ozone/src/lexicon/index.ts index 6b3b6de582a..cf2c613e686 100644 --- a/packages/ozone/src/lexicon/index.ts +++ b/packages/ozone/src/lexicon/index.ts @@ -30,6 +30,7 @@ import * as ComAtprotoAdminSearchRepos from './types/com/atproto/admin/searchRep import * as ComAtprotoAdminSendEmail from './types/com/atproto/admin/sendEmail' import * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/updateAccountEmail' import * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle' +import * as ComAtprotoAdminUpdateAccountPassword from './types/com/atproto/admin/updateAccountPassword' import * as ComAtprotoAdminUpdateCommunicationTemplate from './types/com/atproto/admin/updateCommunicationTemplate' import * as ComAtprotoAdminUpdateSubjectStatus from './types/com/atproto/admin/updateSubjectStatus' import * as ComAtprotoIdentityGetRecommendedDidCredentials from './types/com/atproto/identity/getRecommendedDidCredentials' @@ -444,6 +445,17 @@ export class ComAtprotoAdminNS { return this._server.xrpc.method(nsid, cfg) } + updateAccountPassword( + cfg: ConfigOf< + AV, + ComAtprotoAdminUpdateAccountPassword.Handler>, + ComAtprotoAdminUpdateAccountPassword.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.admin.updateAccountPassword' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + updateCommunicationTemplate( cfg: ConfigOf< AV, diff --git a/packages/ozone/src/lexicon/lexicons.ts b/packages/ozone/src/lexicon/lexicons.ts index 88c27c79b18..77bedd176bb 100644 --- a/packages/ozone/src/lexicon/lexicons.ts +++ b/packages/ozone/src/lexicon/lexicons.ts @@ -1863,6 +1863,33 @@ export const schemaDict = { }, }, }, + ComAtprotoAdminUpdateAccountPassword: { + lexicon: 1, + id: 'com.atproto.admin.updateAccountPassword', + defs: { + main: { + type: 'procedure', + description: + 'Update the password for a user account as an administrator.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['did', 'password'], + properties: { + did: { + type: 'string', + format: 'did', + }, + password: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, ComAtprotoAdminUpdateCommunicationTemplate: { lexicon: 1, id: 'com.atproto.admin.updateCommunicationTemplate', @@ -8860,6 +8887,8 @@ export const ids = { ComAtprotoAdminSendEmail: 'com.atproto.admin.sendEmail', ComAtprotoAdminUpdateAccountEmail: 'com.atproto.admin.updateAccountEmail', ComAtprotoAdminUpdateAccountHandle: 'com.atproto.admin.updateAccountHandle', + ComAtprotoAdminUpdateAccountPassword: + 'com.atproto.admin.updateAccountPassword', ComAtprotoAdminUpdateCommunicationTemplate: 'com.atproto.admin.updateCommunicationTemplate', ComAtprotoAdminUpdateSubjectStatus: 'com.atproto.admin.updateSubjectStatus', diff --git a/packages/ozone/src/lexicon/types/com/atproto/admin/updateAccountPassword.ts b/packages/ozone/src/lexicon/types/com/atproto/admin/updateAccountPassword.ts new file mode 100644 index 00000000000..948568f0d3d --- /dev/null +++ b/packages/ozone/src/lexicon/types/com/atproto/admin/updateAccountPassword.ts @@ -0,0 +1,39 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + did: string + password: string + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/account-manager/index.ts b/packages/pds/src/account-manager/index.ts index d071550ee69..89fb4ac00b1 100644 --- a/packages/pds/src/account-manager/index.ts +++ b/packages/pds/src/account-manager/index.ts @@ -370,6 +370,11 @@ export class AccountManager { 'reset_password', opts.token, ) + await this.updateAccountPassword({ did, password: opts.password }) + } + + async updateAccountPassword(opts: { did: string; password: string }) { + const { did } = opts const passwordScrypt = await scrypt.genSaltAndHash(opts.password) await this.db.transaction(async (dbTxn) => Promise.all([ diff --git a/packages/pds/src/api/com/atproto/admin/index.ts b/packages/pds/src/api/com/atproto/admin/index.ts index cca12226f4b..b0911afa66c 100644 --- a/packages/pds/src/api/com/atproto/admin/index.ts +++ b/packages/pds/src/api/com/atproto/admin/index.ts @@ -15,6 +15,7 @@ import disableInviteCodes from './disableInviteCodes' import getInviteCodes from './getInviteCodes' import updateAccountHandle from './updateAccountHandle' import updateAccountEmail from './updateAccountEmail' +import updateAccountPassword from './updateAccountPassword' import sendEmail from './sendEmail' import deleteAccount from './deleteAccount' import queryModerationStatuses from './queryModerationStatuses' @@ -40,6 +41,7 @@ export default function (server: Server, ctx: AppContext) { getInviteCodes(server, ctx) updateAccountHandle(server, ctx) updateAccountEmail(server, ctx) + updateAccountPassword(server, ctx) sendEmail(server, ctx) deleteAccount(server, ctx) listCommunicationTemplates(server, ctx) diff --git a/packages/pds/src/api/com/atproto/admin/updateAccountPassword.ts b/packages/pds/src/api/com/atproto/admin/updateAccountPassword.ts new file mode 100644 index 00000000000..294286c3873 --- /dev/null +++ b/packages/pds/src/api/com/atproto/admin/updateAccountPassword.ts @@ -0,0 +1,28 @@ +import { AuthRequiredError } from '@atproto/xrpc-server' +import { Server } from '../../../../lexicon' +import AppContext from '../../../../context' +import { authPassthru } from '../../../proxy' + +export default function (server: Server, ctx: AppContext) { + server.com.atproto.admin.updateAccountPassword({ + auth: ctx.authVerifier.role, + handler: async ({ input, auth, req }) => { + if (!auth.credentials.admin) { + throw new AuthRequiredError( + 'Must be an admin to update an account password', + ) + } + + if (ctx.entrywayAgent) { + await ctx.entrywayAgent.com.atproto.admin.updateAccountPassword( + input.body, + authPassthru(req, true), + ) + return + } + + const { did, password } = input.body + await ctx.accountManager.updateAccountPassword({ did, password }) + }, + }) +} diff --git a/packages/pds/src/lexicon/index.ts b/packages/pds/src/lexicon/index.ts index 6b3b6de582a..cf2c613e686 100644 --- a/packages/pds/src/lexicon/index.ts +++ b/packages/pds/src/lexicon/index.ts @@ -30,6 +30,7 @@ import * as ComAtprotoAdminSearchRepos from './types/com/atproto/admin/searchRep import * as ComAtprotoAdminSendEmail from './types/com/atproto/admin/sendEmail' import * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/updateAccountEmail' import * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle' +import * as ComAtprotoAdminUpdateAccountPassword from './types/com/atproto/admin/updateAccountPassword' import * as ComAtprotoAdminUpdateCommunicationTemplate from './types/com/atproto/admin/updateCommunicationTemplate' import * as ComAtprotoAdminUpdateSubjectStatus from './types/com/atproto/admin/updateSubjectStatus' import * as ComAtprotoIdentityGetRecommendedDidCredentials from './types/com/atproto/identity/getRecommendedDidCredentials' @@ -444,6 +445,17 @@ export class ComAtprotoAdminNS { return this._server.xrpc.method(nsid, cfg) } + updateAccountPassword( + cfg: ConfigOf< + AV, + ComAtprotoAdminUpdateAccountPassword.Handler>, + ComAtprotoAdminUpdateAccountPassword.HandlerReqCtx> + >, + ) { + const nsid = 'com.atproto.admin.updateAccountPassword' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + updateCommunicationTemplate( cfg: ConfigOf< AV, diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index 88c27c79b18..77bedd176bb 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -1863,6 +1863,33 @@ export const schemaDict = { }, }, }, + ComAtprotoAdminUpdateAccountPassword: { + lexicon: 1, + id: 'com.atproto.admin.updateAccountPassword', + defs: { + main: { + type: 'procedure', + description: + 'Update the password for a user account as an administrator.', + input: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['did', 'password'], + properties: { + did: { + type: 'string', + format: 'did', + }, + password: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, ComAtprotoAdminUpdateCommunicationTemplate: { lexicon: 1, id: 'com.atproto.admin.updateCommunicationTemplate', @@ -8860,6 +8887,8 @@ export const ids = { ComAtprotoAdminSendEmail: 'com.atproto.admin.sendEmail', ComAtprotoAdminUpdateAccountEmail: 'com.atproto.admin.updateAccountEmail', ComAtprotoAdminUpdateAccountHandle: 'com.atproto.admin.updateAccountHandle', + ComAtprotoAdminUpdateAccountPassword: + 'com.atproto.admin.updateAccountPassword', ComAtprotoAdminUpdateCommunicationTemplate: 'com.atproto.admin.updateCommunicationTemplate', ComAtprotoAdminUpdateSubjectStatus: 'com.atproto.admin.updateSubjectStatus', diff --git a/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountPassword.ts b/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountPassword.ts new file mode 100644 index 00000000000..948568f0d3d --- /dev/null +++ b/packages/pds/src/lexicon/types/com/atproto/admin/updateAccountPassword.ts @@ -0,0 +1,39 @@ +/** + * GENERATED CODE - DO NOT MODIFY + */ +import express from 'express' +import { ValidationResult, BlobRef } from '@atproto/lexicon' +import { lexicons } from '../../../../lexicons' +import { isObj, hasProp } from '../../../../util' +import { CID } from 'multiformats/cid' +import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server' + +export interface QueryParams {} + +export interface InputSchema { + did: string + password: string + [k: string]: unknown +} + +export interface HandlerInput { + encoding: 'application/json' + body: InputSchema +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | void +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/tests/account.test.ts b/packages/pds/tests/account.test.ts index 70ca5abf741..743248814f8 100644 --- a/packages/pds/tests/account.test.ts +++ b/packages/pds/tests/account.test.ts @@ -559,4 +559,46 @@ describe('account', () => { }), ).resolves.toBeDefined() }) + + it('allows an admin to update password', async () => { + const tryUnauthed = agent.api.com.atproto.admin.updateAccountPassword({ + did, + password: 'new-admin-pass', + }) + await expect(tryUnauthed).rejects.toThrow('Authentication Required') + + const tryAsModerator = agent.api.com.atproto.admin.updateAccountPassword( + { did, password: 'new-admin-pass' }, + { + headers: network.pds.adminAuthHeaders('moderator'), + encoding: 'application/json', + }, + ) + await expect(tryAsModerator).rejects.toThrow( + 'Must be an admin to update an account password', + ) + + await agent.api.com.atproto.admin.updateAccountPassword( + { did, password: 'new-admin-password' }, + { + headers: network.pds.adminAuthHeaders('admin'), + encoding: 'application/json', + }, + ) + + // old password fails + await expect( + agent.api.com.atproto.server.createSession({ + identifier: did, + password, + }), + ).rejects.toThrow('Invalid identifier or password') + + await expect( + agent.api.com.atproto.server.createSession({ + identifier: did, + password: 'new-admin-password', + }), + ).resolves.toBeDefined() + }) }) From 6d38eea8f21cb80913d7d18200ca106a21db30df Mon Sep 17 00:00:00 2001 From: Daniel Holmgren Date: Thu, 22 Feb 2024 08:58:18 -0600 Subject: [PATCH 027/103] Config for blob upload limit (#2213) config var for blob upload limit --- packages/pds/src/config/config.ts | 4 +++- packages/pds/src/config/env.ts | 2 ++ packages/pds/src/index.ts | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/pds/src/config/config.ts b/packages/pds/src/config/config.ts index e433f7ed251..8ca807d39b5 100644 --- a/packages/pds/src/config/config.ts +++ b/packages/pds/src/config/config.ts @@ -23,6 +23,7 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => { privacyPolicyUrl: env.privacyPolicyUrl, termsOfServiceUrl: env.termsOfServiceUrl, acceptingImports: env.acceptingImports ?? true, + blobUploadLimit: env.blobUploadLimit ?? 5 * 1024 * 1024, // 5mb } const dbLoc = (name: string) => { @@ -275,9 +276,10 @@ export type ServiceConfig = { publicUrl: string did: string version?: string - acceptingImports: boolean privacyPolicyUrl?: string termsOfServiceUrl?: string + acceptingImports: boolean + blobUploadLimit: number } export type DatabaseConfig = { diff --git a/packages/pds/src/config/env.ts b/packages/pds/src/config/env.ts index abe1904ad5f..fb5aed8232f 100644 --- a/packages/pds/src/config/env.ts +++ b/packages/pds/src/config/env.ts @@ -10,6 +10,7 @@ export const readEnv = (): ServerEnvironment => { privacyPolicyUrl: envStr('PDS_PRIVACY_POLICY_URL'), termsOfServiceUrl: envStr('PDS_TERMS_OF_SERVICE_URL'), acceptingImports: envBool('PDS_ACCEPTING_REPO_IMPORTS'), + blobUploadLimit: envInt('PDS_BLOB_UPLOAD_LIMIT'), // database dataDirectory: envStr('PDS_DATA_DIRECTORY'), @@ -116,6 +117,7 @@ export type ServerEnvironment = { privacyPolicyUrl?: string termsOfServiceUrl?: string acceptingImports?: boolean + blobUploadLimit?: number // database dataDirectory?: string diff --git a/packages/pds/src/index.ts b/packages/pds/src/index.ts index 77f5948e8aa..ea7c7aa53ee 100644 --- a/packages/pds/src/index.ts +++ b/packages/pds/src/index.ts @@ -63,7 +63,7 @@ export class PDS { payload: { jsonLimit: 100 * 1024, // 100kb textLimit: 100 * 1024, // 100kb - blobLimit: 5 * 1024 * 1024, // 5mb + blobLimit: cfg.service.blobUploadLimit, }, } if (cfg.rateLimits.enabled) { From 514aab92d26acd43859285f46318e386846522b1 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 22 Feb 2024 09:43:58 -0600 Subject: [PATCH 028/103] Add missing `getPreferences` union return types (#2215) * Add missing getPreferences union types * Add changeset --- .changeset/seven-ladybugs-attend.md | 5 +++++ lexicons/app/bsky/actor/defs.json | 4 +++- packages/api/src/client/lexicons.ts | 2 ++ packages/api/src/client/types/app/bsky/actor/defs.ts | 2 ++ packages/bsky/src/lexicon/lexicons.ts | 2 ++ packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts | 2 ++ packages/ozone/src/lexicon/lexicons.ts | 2 ++ packages/ozone/src/lexicon/types/app/bsky/actor/defs.ts | 2 ++ packages/pds/src/lexicon/lexicons.ts | 2 ++ packages/pds/src/lexicon/types/app/bsky/actor/defs.ts | 2 ++ 10 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 .changeset/seven-ladybugs-attend.md diff --git a/.changeset/seven-ladybugs-attend.md b/.changeset/seven-ladybugs-attend.md new file mode 100644 index 00000000000..58ed51a5153 --- /dev/null +++ b/.changeset/seven-ladybugs-attend.md @@ -0,0 +1,5 @@ +--- +'@atproto/api': patch +--- + +Add missing `getPreferences` union return types diff --git a/lexicons/app/bsky/actor/defs.json b/lexicons/app/bsky/actor/defs.json index a260fc4ef5f..4764c7c14ae 100644 --- a/lexicons/app/bsky/actor/defs.json +++ b/lexicons/app/bsky/actor/defs.json @@ -105,7 +105,9 @@ "#personalDetailsPref", "#feedViewPref", "#threadViewPref", - "#interestsPref" + "#interestsPref", + "#mutedWordsPref", + "#hiddenPostsPref" ] } }, diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index 77bedd176bb..d546b70b987 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -5102,6 +5102,8 @@ export const schemaDict = { 'lex:app.bsky.actor.defs#feedViewPref', 'lex:app.bsky.actor.defs#threadViewPref', 'lex:app.bsky.actor.defs#interestsPref', + 'lex:app.bsky.actor.defs#mutedWordsPref', + 'lex:app.bsky.actor.defs#hiddenPostsPref', ], }, }, diff --git a/packages/api/src/client/types/app/bsky/actor/defs.ts b/packages/api/src/client/types/app/bsky/actor/defs.ts index 535cfe9094f..630f2454056 100644 --- a/packages/api/src/client/types/app/bsky/actor/defs.ts +++ b/packages/api/src/client/types/app/bsky/actor/defs.ts @@ -114,6 +114,8 @@ export type Preferences = ( | FeedViewPref | ThreadViewPref | InterestsPref + | MutedWordsPref + | HiddenPostsPref | { $type: string; [k: string]: unknown } )[] diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index 77bedd176bb..d546b70b987 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -5102,6 +5102,8 @@ export const schemaDict = { 'lex:app.bsky.actor.defs#feedViewPref', 'lex:app.bsky.actor.defs#threadViewPref', 'lex:app.bsky.actor.defs#interestsPref', + 'lex:app.bsky.actor.defs#mutedWordsPref', + 'lex:app.bsky.actor.defs#hiddenPostsPref', ], }, }, diff --git a/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts b/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts index 50afff62a0f..6836fa7e516 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/actor/defs.ts @@ -114,6 +114,8 @@ export type Preferences = ( | FeedViewPref | ThreadViewPref | InterestsPref + | MutedWordsPref + | HiddenPostsPref | { $type: string; [k: string]: unknown } )[] diff --git a/packages/ozone/src/lexicon/lexicons.ts b/packages/ozone/src/lexicon/lexicons.ts index 77bedd176bb..d546b70b987 100644 --- a/packages/ozone/src/lexicon/lexicons.ts +++ b/packages/ozone/src/lexicon/lexicons.ts @@ -5102,6 +5102,8 @@ export const schemaDict = { 'lex:app.bsky.actor.defs#feedViewPref', 'lex:app.bsky.actor.defs#threadViewPref', 'lex:app.bsky.actor.defs#interestsPref', + 'lex:app.bsky.actor.defs#mutedWordsPref', + 'lex:app.bsky.actor.defs#hiddenPostsPref', ], }, }, diff --git a/packages/ozone/src/lexicon/types/app/bsky/actor/defs.ts b/packages/ozone/src/lexicon/types/app/bsky/actor/defs.ts index 50afff62a0f..6836fa7e516 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/actor/defs.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/actor/defs.ts @@ -114,6 +114,8 @@ export type Preferences = ( | FeedViewPref | ThreadViewPref | InterestsPref + | MutedWordsPref + | HiddenPostsPref | { $type: string; [k: string]: unknown } )[] diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index 77bedd176bb..d546b70b987 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -5102,6 +5102,8 @@ export const schemaDict = { 'lex:app.bsky.actor.defs#feedViewPref', 'lex:app.bsky.actor.defs#threadViewPref', 'lex:app.bsky.actor.defs#interestsPref', + 'lex:app.bsky.actor.defs#mutedWordsPref', + 'lex:app.bsky.actor.defs#hiddenPostsPref', ], }, }, diff --git a/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts b/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts index 50afff62a0f..6836fa7e516 100644 --- a/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts +++ b/packages/pds/src/lexicon/types/app/bsky/actor/defs.ts @@ -114,6 +114,8 @@ export type Preferences = ( | FeedViewPref | ThreadViewPref | InterestsPref + | MutedWordsPref + | HiddenPostsPref | { $type: string; [k: string]: unknown } )[] From 433d47d6a409e61ffc32e5fffe4619afaddc6d25 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 09:46:27 -0600 Subject: [PATCH 029/103] Version packages (#2216) Co-authored-by: github-actions[bot] --- .changeset/seven-ladybugs-attend.md | 5 ----- packages/api/CHANGELOG.md | 6 ++++++ packages/api/package.json | 2 +- packages/bsky/CHANGELOG.md | 7 +++++++ packages/bsky/package.json | 2 +- packages/dev-env/CHANGELOG.md | 10 ++++++++++ packages/dev-env/package.json | 2 +- packages/ozone/CHANGELOG.md | 7 +++++++ packages/ozone/package.json | 2 +- packages/pds/CHANGELOG.md | 7 +++++++ packages/pds/package.json | 2 +- 11 files changed, 42 insertions(+), 10 deletions(-) delete mode 100644 .changeset/seven-ladybugs-attend.md diff --git a/.changeset/seven-ladybugs-attend.md b/.changeset/seven-ladybugs-attend.md deleted file mode 100644 index 58ed51a5153..00000000000 --- a/.changeset/seven-ladybugs-attend.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@atproto/api': patch ---- - -Add missing `getPreferences` union return types diff --git a/packages/api/CHANGELOG.md b/packages/api/CHANGELOG.md index 64d884e0969..58732dbd074 100644 --- a/packages/api/CHANGELOG.md +++ b/packages/api/CHANGELOG.md @@ -1,5 +1,11 @@ # @atproto/api +## 0.10.1 + +### Patch Changes + +- [#2215](https://github.com/bluesky-social/atproto/pull/2215) [`514aab92d`](https://github.com/bluesky-social/atproto/commit/514aab92d26acd43859285f46318e386846522b1) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Add missing `getPreferences` union return types + ## 0.10.0 ### Minor Changes diff --git a/packages/api/package.json b/packages/api/package.json index c49ef290d46..ab76c7b4249 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/api", - "version": "0.10.0", + "version": "0.10.1", "license": "MIT", "description": "Client library for atproto and Bluesky", "keywords": [ diff --git a/packages/bsky/CHANGELOG.md b/packages/bsky/CHANGELOG.md index b3181272bee..91a5493cb3f 100644 --- a/packages/bsky/CHANGELOG.md +++ b/packages/bsky/CHANGELOG.md @@ -1,5 +1,12 @@ # @atproto/bsky +## 0.0.33 + +### Patch Changes + +- Updated dependencies [[`514aab92d`](https://github.com/bluesky-social/atproto/commit/514aab92d26acd43859285f46318e386846522b1)]: + - @atproto/api@0.10.1 + ## 0.0.32 ### Patch Changes diff --git a/packages/bsky/package.json b/packages/bsky/package.json index 3402fd187b2..ec6f8f1feb3 100644 --- a/packages/bsky/package.json +++ b/packages/bsky/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/bsky", - "version": "0.0.32", + "version": "0.0.33", "license": "MIT", "description": "Reference implementation of app.bsky App View (Bluesky API)", "keywords": [ diff --git a/packages/dev-env/CHANGELOG.md b/packages/dev-env/CHANGELOG.md index 41f20d87af3..dd5af8ef139 100644 --- a/packages/dev-env/CHANGELOG.md +++ b/packages/dev-env/CHANGELOG.md @@ -1,5 +1,15 @@ # @atproto/dev-env +## 0.2.33 + +### Patch Changes + +- Updated dependencies [[`514aab92d`](https://github.com/bluesky-social/atproto/commit/514aab92d26acd43859285f46318e386846522b1)]: + - @atproto/api@0.10.1 + - @atproto/bsky@0.0.33 + - @atproto/ozone@0.0.12 + - @atproto/pds@0.4.1 + ## 0.2.32 ### Patch Changes diff --git a/packages/dev-env/package.json b/packages/dev-env/package.json index 7e99e225645..ee5165e9273 100644 --- a/packages/dev-env/package.json +++ b/packages/dev-env/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/dev-env", - "version": "0.2.32", + "version": "0.2.33", "license": "MIT", "description": "Local development environment helper for atproto development", "keywords": [ diff --git a/packages/ozone/CHANGELOG.md b/packages/ozone/CHANGELOG.md index dd88ccf6fc8..ca4d4954d97 100644 --- a/packages/ozone/CHANGELOG.md +++ b/packages/ozone/CHANGELOG.md @@ -1,5 +1,12 @@ # @atproto/ozone +## 0.0.12 + +### Patch Changes + +- Updated dependencies [[`514aab92d`](https://github.com/bluesky-social/atproto/commit/514aab92d26acd43859285f46318e386846522b1)]: + - @atproto/api@0.10.1 + ## 0.0.11 ### Patch Changes diff --git a/packages/ozone/package.json b/packages/ozone/package.json index 0f160259644..e76dc644f10 100644 --- a/packages/ozone/package.json +++ b/packages/ozone/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/ozone", - "version": "0.0.11", + "version": "0.0.12", "license": "MIT", "description": "Backend service for moderating the Bluesky network.", "keywords": [ diff --git a/packages/pds/CHANGELOG.md b/packages/pds/CHANGELOG.md index c131d37fa01..8d2e19210b8 100644 --- a/packages/pds/CHANGELOG.md +++ b/packages/pds/CHANGELOG.md @@ -1,5 +1,12 @@ # @atproto/pds +## 0.4.1 + +### Patch Changes + +- Updated dependencies [[`514aab92d`](https://github.com/bluesky-social/atproto/commit/514aab92d26acd43859285f46318e386846522b1)]: + - @atproto/api@0.10.1 + ## 0.4.0 ### Patch Changes diff --git a/packages/pds/package.json b/packages/pds/package.json index c620132ed25..74935d75057 100644 --- a/packages/pds/package.json +++ b/packages/pds/package.json @@ -1,6 +1,6 @@ { "name": "@atproto/pds", - "version": "0.4.0", + "version": "0.4.1", "license": "MIT", "description": "Reference implementation of atproto Personal Data Server (PDS)", "keywords": [ From 43531905ce1aec6d36d9be5943782811ecca6e6d Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Thu, 22 Feb 2024 16:25:06 -0600 Subject: [PATCH 030/103] Fix sanitization, improve test to catch (#2218) * Fix sanitization, improve test to catch * Add changeset --- .changeset/lovely-dogs-run.md | 5 +++++ packages/api/src/bsky-agent.ts | 10 +++++----- packages/api/tests/bsky-agent.test.ts | 9 ++++++++- 3 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 .changeset/lovely-dogs-run.md diff --git a/.changeset/lovely-dogs-run.md b/.changeset/lovely-dogs-run.md new file mode 100644 index 00000000000..b2ac95215b6 --- /dev/null +++ b/.changeset/lovely-dogs-run.md @@ -0,0 +1,5 @@ +--- +'@atproto/api': patch +--- + +Fix mute word upsert logic by ensuring we're comparing sanitized word values diff --git a/packages/api/src/bsky-agent.ts b/packages/api/src/bsky-agent.ts index 291cf8608c4..305348bea0b 100644 --- a/packages/api/src/bsky-agent.ts +++ b/packages/api/src/bsky-agent.ts @@ -668,24 +668,24 @@ async function updateMutedWords( if (mutedWordsPref && AppBskyActorDefs.isMutedWordsPref(mutedWordsPref)) { if (action === 'upsert' || action === 'update') { - for (const newItem of mutedWords) { + for (const word of mutedWords) { let foundMatch = false for (const existingItem of mutedWordsPref.items) { - if (existingItem.value === newItem.value) { + if (existingItem.value === sanitizeMutedWord(word).value) { existingItem.targets = action === 'upsert' ? Array.from( - new Set([...existingItem.targets, ...newItem.targets]), + new Set([...existingItem.targets, ...word.targets]), ) - : newItem.targets + : word.targets foundMatch = true break } } if (action === 'upsert' && !foundMatch) { - mutedWordsPref.items.push(sanitizeMutedWord(newItem)) + mutedWordsPref.items.push(sanitizeMutedWord(word)) } } } else if (action === 'remove') { diff --git a/packages/api/tests/bsky-agent.test.ts b/packages/api/tests/bsky-agent.test.ts index edaf3912ec7..db8ee0a1567 100644 --- a/packages/api/tests/bsky-agent.test.ts +++ b/packages/api/tests/bsky-agent.test.ts @@ -1199,10 +1199,17 @@ describe('agent', () => { }) it('upsertMutedWords with #', async () => { + await agent.upsertMutedWords([ + { value: 'hashtag', targets: ['content'] }, + ]) await agent.upsertMutedWords([{ value: '#hashtag', targets: ['tag'] }]) const { mutedWords } = await agent.getPreferences() expect(mutedWords.find((m) => m.value === '#hashtag')).toBeFalsy() - expect(mutedWords.find((m) => m.value === 'hashtag')).toBeTruthy() + expect(mutedWords.find((m) => m.value === 'hashtag')).toStrictEqual({ + value: 'hashtag', + targets: ['content', 'tag'], + }) + expect(mutedWords.filter((m) => m.value === 'hashtag').length).toBe(1) }) it('updateMutedWord', async () => { From 0c815b964c030aa0f277c40bf9786f130dc320f4 Mon Sep 17 00:00:00 2001 From: bnewbold Date: Fri, 23 Feb 2024 14:15:17 -0800 Subject: [PATCH 031/103] syntax: allow colon in record-key (#2223) * syntax: allow colon in record-key * changeset for rkey colon change --- .changeset/cuddly-adults-beg.md | 5 +++++ interop-test-files/syntax/aturi_syntax_valid.txt | 8 ++++++++ .../syntax/recordkey_syntax_invalid.txt | 5 +++-- .../syntax/recordkey_syntax_valid.txt | 13 +++++++++++++ packages/syntax/src/recordkey.ts | 2 +- 5 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 .changeset/cuddly-adults-beg.md diff --git a/.changeset/cuddly-adults-beg.md b/.changeset/cuddly-adults-beg.md new file mode 100644 index 00000000000..b47c8101273 --- /dev/null +++ b/.changeset/cuddly-adults-beg.md @@ -0,0 +1,5 @@ +--- +'@atproto/syntax': minor +--- + +allow colon character in record-key syntax diff --git a/interop-test-files/syntax/aturi_syntax_valid.txt b/interop-test-files/syntax/aturi_syntax_valid.txt index 2552a964ce0..fd4523d0de9 100644 --- a/interop-test-files/syntax/aturi_syntax_valid.txt +++ b/interop-test-files/syntax/aturi_syntax_valid.txt @@ -24,3 +24,11 @@ at://did:plc:asdf123/com.atproto.feed.post/a at://did:plc:asdf123/com.atproto.feed.post/asdf-123 at://did:abc:123 at://did:abc:123/io.nsid.someFunc/record-key + +at://did:abc:123/io.nsid.someFunc/self. +at://did:abc:123/io.nsid.someFunc/lang: +at://did:abc:123/io.nsid.someFunc/: +at://did:abc:123/io.nsid.someFunc/- +at://did:abc:123/io.nsid.someFunc/_ +at://did:abc:123/io.nsid.someFunc/~ +at://did:abc:123/io.nsid.someFunc/... diff --git a/interop-test-files/syntax/recordkey_syntax_invalid.txt b/interop-test-files/syntax/recordkey_syntax_invalid.txt index 1da3d1e7dbc..52106d873a0 100644 --- a/interop-test-files/syntax/recordkey_syntax_invalid.txt +++ b/interop-test-files/syntax/recordkey_syntax_invalid.txt @@ -1,5 +1,4 @@ # specs -literal:self alpha/beta . .. @@ -10,5 +9,7 @@ any+space number[3] number(3) "quote" -pre:fix dHJ1ZQ== + +# too long: 'o'.repeat(513) +ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo diff --git a/interop-test-files/syntax/recordkey_syntax_valid.txt b/interop-test-files/syntax/recordkey_syntax_valid.txt index 8d77d04d2b7..92e8b7e31c9 100644 --- a/interop-test-files/syntax/recordkey_syntax_valid.txt +++ b/interop-test-files/syntax/recordkey_syntax_valid.txt @@ -3,6 +3,19 @@ self example.com ~1.2-3_ dHJ1ZQ +_ +literal:self +pre:fix + +# more corner-cases +: +- +_ +~ +... +self. +lang: +:lang # very long: 'o'.repeat(512) oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo diff --git a/packages/syntax/src/recordkey.ts b/packages/syntax/src/recordkey.ts index 6d424a1b8b4..a12668ff1e5 100644 --- a/packages/syntax/src/recordkey.ts +++ b/packages/syntax/src/recordkey.ts @@ -3,7 +3,7 @@ export const ensureValidRecordKey = (rkey: string): void => { throw new InvalidRecordKeyError('record key must be 1 to 512 characters') } // simple regex to enforce most constraints via just regex and length. - if (!/^[a-zA-Z0-9_~.-]{1,512}$/.test(rkey)) { + if (!/^[a-zA-Z0-9_~.:-]{1,512}$/.test(rkey)) { throw new InvalidRecordKeyError('record key syntax not valid (regex)') } if (rkey == '.' || rkey == '..') From d643b5bb13e98275a7319bffdc9e89ac0e6a9c00 Mon Sep 17 00:00:00 2001 From: bnewbold Date: Fri, 23 Feb 2024 14:59:47 -0800 Subject: [PATCH 032/103] lex: tweak deprecation note (#2222) * lex: tweak deprecation note The motivation with this is that the docs site hides deprecated endpoint, and the behavior was updated to only do this when "deprecation" is the first word of the description (to avoid hiding some endpoints which just talk about deprecation). * make codegen --- lexicons/com/atproto/temp/fetchLabels.json | 2 +- packages/api/src/client/lexicons.ts | 2 +- packages/bsky/src/lexicon/lexicons.ts | 2 +- packages/ozone/src/lexicon/lexicons.ts | 2 +- packages/pds/src/lexicon/lexicons.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lexicons/com/atproto/temp/fetchLabels.json b/lexicons/com/atproto/temp/fetchLabels.json index d76041ac296..57c3f732cdb 100644 --- a/lexicons/com/atproto/temp/fetchLabels.json +++ b/lexicons/com/atproto/temp/fetchLabels.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Fetch all labels from a labeler created after a certain date. DEPRECATED: use queryLabels or subscribeLabels instead", + "description": "DEPRECATED: use queryLabels or subscribeLabels instead -- Fetch all labels from a labeler created after a certain date.", "parameters": { "type": "params", "properties": { diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index d546b70b987..14e4c1cb81e 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -4867,7 +4867,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Fetch all labels from a labeler created after a certain date. DEPRECATED: use queryLabels or subscribeLabels instead', + 'DEPRECATED: use queryLabels or subscribeLabels instead -- Fetch all labels from a labeler created after a certain date.', parameters: { type: 'params', properties: { diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index d546b70b987..14e4c1cb81e 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -4867,7 +4867,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Fetch all labels from a labeler created after a certain date. DEPRECATED: use queryLabels or subscribeLabels instead', + 'DEPRECATED: use queryLabels or subscribeLabels instead -- Fetch all labels from a labeler created after a certain date.', parameters: { type: 'params', properties: { diff --git a/packages/ozone/src/lexicon/lexicons.ts b/packages/ozone/src/lexicon/lexicons.ts index d546b70b987..14e4c1cb81e 100644 --- a/packages/ozone/src/lexicon/lexicons.ts +++ b/packages/ozone/src/lexicon/lexicons.ts @@ -4867,7 +4867,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Fetch all labels from a labeler created after a certain date. DEPRECATED: use queryLabels or subscribeLabels instead', + 'DEPRECATED: use queryLabels or subscribeLabels instead -- Fetch all labels from a labeler created after a certain date.', parameters: { type: 'params', properties: { diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index d546b70b987..14e4c1cb81e 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -4867,7 +4867,7 @@ export const schemaDict = { main: { type: 'query', description: - 'Fetch all labels from a labeler created after a certain date. DEPRECATED: use queryLabels or subscribeLabels instead', + 'DEPRECATED: use queryLabels or subscribeLabels instead -- Fetch all labels from a labeler created after a certain date.', parameters: { type: 'params', properties: { From 53122b4b3e2e3e764e152e9a98d7ccd215900ef9 Mon Sep 17 00:00:00 2001 From: Foysal Ahamed Date: Tue, 27 Feb 2024 20:52:58 +0100 Subject: [PATCH 033/103] :sparkles: Move appealed subject's review state to escalated (#2198) --- packages/ozone/src/mod-service/status.ts | 3 +++ .../ozone/tests/moderation-appeals.test.ts | 26 ++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/packages/ozone/src/mod-service/status.ts b/packages/ozone/src/mod-service/status.ts index 2edba64282f..b79160ccdc2 100644 --- a/packages/ozone/src/mod-service/status.ts +++ b/packages/ozone/src/mod-service/status.ts @@ -179,6 +179,9 @@ export const adjustModerationSubjectStatus = async ( subjectStatus.appealed = true newStatus.lastAppealedAt = createdAt subjectStatus.lastAppealedAt = createdAt + // Set reviewState to escalated when appeal events are emitted + subjectStatus.reviewState = REVIEWESCALATED + newStatus.reviewState = REVIEWESCALATED } if ( diff --git a/packages/ozone/tests/moderation-appeals.test.ts b/packages/ozone/tests/moderation-appeals.test.ts index f21098811a7..31dae61f9ef 100644 --- a/packages/ozone/tests/moderation-appeals.test.ts +++ b/packages/ozone/tests/moderation-appeals.test.ts @@ -118,7 +118,7 @@ describe('moderation-appeals', () => { }) // Verify that appeal status changed when appeal report was emitted by moderator - const status = await assertBobsPostStatus(REVIEWOPEN, true) + const status = await assertBobsPostStatus(REVIEWESCALATED, true) expect(status?.appealedAt).not.toBeNull() // Create a report as normal user for carol's post @@ -143,7 +143,11 @@ describe('moderation-appeals', () => { subject: getCarolPostSubject(), }) // Verify that the appeal status on carol's post is true - await assertSubjectStatus(getCarolPostSubject().uri, REVIEWOPEN, true) + await assertSubjectStatus( + getCarolPostSubject().uri, + REVIEWESCALATED, + true, + ) }) it('allows multiple appeals and updates last appealed timestamp', async () => { // Resolve appeal with acknowledge @@ -155,7 +159,7 @@ describe('moderation-appeals', () => { createdBy: sc.dids.carol, }) - const previousStatus = await assertBobsPostStatus(REVIEWOPEN, false) + const previousStatus = await assertBobsPostStatus(REVIEWESCALATED, false) await emitModerationEvent({ event: { @@ -167,7 +171,7 @@ describe('moderation-appeals', () => { }) // Verify that even after the appeal event by bob for his post, the appeal status is true again with new timestamp - const newStatus = await assertBobsPostStatus(REVIEWOPEN, true) + const newStatus = await assertBobsPostStatus(REVIEWESCALATED, true) expect( new Date(`${previousStatus?.lastAppealedAt}`).getTime(), ).toBeLessThan(new Date(`${newStatus?.lastAppealedAt}`).getTime()) @@ -210,7 +214,11 @@ describe('moderation-appeals', () => { createdBy: sc.dids.alice, }) - await assertSubjectStatus(getAlicesPostSubject().uri, REVIEWOPEN, true) + await assertSubjectStatus( + getAlicesPostSubject().uri, + REVIEWESCALATED, + true, + ) // Bob reports it again await emitModerationEvent({ @@ -222,8 +230,12 @@ describe('moderation-appeals', () => { createdBy: sc.dids.bob, }) - // Assert that the status is still REVIEWOPEN, as report events are meant to do - await assertSubjectStatus(getAlicesPostSubject().uri, REVIEWOPEN, true) + // Assert that the status is still REVIEWESCALATED, as report events are meant to do + await assertSubjectStatus( + getAlicesPostSubject().uri, + REVIEWESCALATED, + true, + ) // Emit an escalation event await emitModerationEvent({ From f65de89eed3d90190b0944655df9c1898cb31e0d Mon Sep 17 00:00:00 2001 From: Daniel Holmgren Date: Tue, 27 Feb 2024 14:22:55 -0600 Subject: [PATCH 034/103] Feature: Appview v2 (#1924) * add buf & connectrpc, codegen client * lint * prettier ignore * fix prettier ignore * tidy & add tests * filler commit * rm filler * server boilerplate * follows impl * posts impl * posts & likes impl * repost impl * profiles & handle null values * list impl * mutes impl * blocks impl * misc * feed gen impl * label impl * notifs impl * feeds impl * threads impl * early sketchwork * wip * stub out thick client * in-progress work on hydrator * tweak * hydrate profile labels, detail lists * feedgen hydration * protobuf tweaks * more protobuf tweaks * wip * snake case * moar snake case * tidy actor hydration * tidy parsing * type fixes, renaming, comments in hydrator * hydrate list items and likes * hydrate notifications * feed hydration * graph & label hydration * more record protobufs * pluralize * tweak pbs * use new methods * Setup dataplane grpc client/mock server (#1921) * add buf & connectrpc, codegen client * lint * prettier ignore * fix prettier ignore * tidy & add tests * add record getter mocks * post view hydration * fix up mock dataplane to match new protos * missed one * wire up dataplane in ctx & dev-env * adding some basic views * feed hydration, add blocks to post hydration * pass over notification hydration * tidy * merge * implement getProfile * hydrate post aggregation and viewer state * fix * fix codegen * get some tests passing! * add takedowns & some like bugfixing * all profile tests passing! * likes test * follow endpoints using data plane * reorg follow block rules * reposts * post views! * implement getList w/ dataplane caveat * adjust dataplane getListMembers to return listitem uris * implement getListMutes and -Blocks w/ dataplane * suggestions * timeline * misc view fixes * view fixes for mutes, self-mute/block * author feed * feed gen routes * tidy * misc block/mute fixes * list feed & actor likes * implement getLists, fix some empty cursors * implement getMutes, empty profile description fix * implement getBlocks, block application fix * implement getSuggestedFollowsByActor, needs some fixes * feed generation * search routes * threads * tidy * fix some snaps * fix getSuggestedFollowsByActor * implement listNotifications * implement getUnreadCount w/ dataplane * implement notifications.updateSeen w/ dataplane * 3rd party blocking tests * blocked profile viewer * add search mocks * refactor getFeed * createPipeline -> createPipelineNew * basic replygating functionality on dataplane w/o filtering violating replies * hack threadgates into dataplane, apply gates * deterministic thread orders in dataplane * misc cleanup around dataplane * upgrade typescript to v5.3 * update typescript linter deps * sync bsky proto, codegen * update dataplane, sync with bsky proto updates * remove indexer, ingester, daemon, moderation services from appview * convert more bsky internals to dataplane, remove custom feedgens, implement mute/unmuting in mock dataplane * remove bsky services. db and indexing logic into mock dataplane. * remove tests not needed by appview v2, misc reorg * add basic in-mem repo subscription to dataplane mock * fix dev-env, bsky tests, bsky build * cull bsky service entrypoint * add bsky service readme * build * tidy * tidy, fix pds proxy tests * fix * fix bsky entrypoint deps * support http2 grpc client * build * fix dataplane bad tls config/default * support multiple dataplane urls, retry when unavailable * build * tidy/fix * move dataplane mock tests into their own dir * cover label hydration through timeline test * bring back labels in appview tests * remove unused db primary/replica/coordinator from bsky dataplane * bsky proto add cids to contracts, buf codegen * sync-up bsky data-plane w/ codegen updates * start using dataplane interaction endpoints * add file * avoid overfetching from dataplane, plumb feed items and cids * pass refs through for post viewer state * switch list feeds to use feed item in dataplane * handle not found err on get-thread dataplane call * support use of search service rather than dataplane methods * mark some appview v2 todos * tidy * still use dataplane on search endpoints when search service is not configured * fix pds test * fix up bsky tests & snaps * tidy migrations * fix appview-v2 docker build * Support label issuer tied to appview v2 (#2033) support label issuer tied to appview * Appview v2: handle empty cursor on list notifications (#2017) handle empty cursor on appview listnotifs * Update appview v2 to use author feed enum (#2047) * update bsky protos with author feed enum, misc feed item changes * support new author feed enums in dataplane * fix build * Appview v2: utilize sorted-at field in bsky protos (#2050) utilize new sorted-at field in bsky protos * remove all dataplane usage of GetLikeCounts, switch to GetInteractionCounts * Appview v2, sync w/ changes to protos (#2071) * sync bsky protos * sync-up bsky implementation w/ proto changes * Appview v2 initial implementation for getPopularFeedGenerators (#2072) add an initial implementation for getPopularFeedGenerators on appview v2 * merge * fixes * fix feed tests * fix bsync mock * format * remove unused config * fix lockfile * another lockfile fix * fix duplicate type * fix dupplicate test * Appview v2 handling clearly bad cursors (#2092) * make mock dataplane cursors different from v1 cursors * fail open on clearly bad appview cursors * fix pds appview proxy snaps * Appview v2 no notifs seen behavior (#2096) * alter behavior for presenting notifications w/ no last-seen time * fix pds proxy tests * Appview v2 dataplane retries based on client host (#2098) choose dataplane client for retries based on host when possible/relevant * don't apply negated labels * display suspensions on actor profile in appview v2 * Appview v2 use dataplane for identity lookups (#2095) * update bsky proto w/ identity methods * setup identity endpoints on mock dataplane * move from idresolver to dataplane for identity lookups on appview * tidy * Appview v2: apply safe takedown refs to records, actors (#2107) apply safe takedown refs to records, actors * Fix timing on appview v2 repo rev header (#2113) fix timing on appview repo rev * fix post thread responses * Appview v2 don't apply 3p self blocks (#2112) do not apply 3p self-blocks * Appview v2 search for feed generators (#2118) * add protos for feedgen search * support feed search on getPopularFeedGenerators * Appview v2 config tidy (#2117) * remove mod and triage roles from appview * rename cdn and search config * remove custom feed harness from appview v2 * Appview v2: don't apply missing modlists (#2122) * dont apply missing mod lists * update mock dataplane * Update packages/bsky/src/hydration/hydrator.ts Co-authored-by: devin ivy * refactor & document a bit better * fix up other routes --------- Co-authored-by: devin ivy * Appview v2 enforce post thread root boundary (#2120) * enforce post thread root boundary * test thread root boundary * Appview v2 fix admin environment variable (#2137) fix admin env in appview v2 * Remove re-pagination from getSuggestions (#2145) * remove re-pagination from getSuggestions * fix test * Adjust wording for account suspension (#2153) adjust wording for account suspension * Appview v2: fix not-found and blocked uris in threads (#2201) * fix uris of not-found and blocked posts in threads * update snaps * :sparkles: Show author feed of takendown author to admins only (#2197) * fold in cid, auth, tracing, node version changes * remove dead config from bsky service entrypoint * build * remove ozone test codepaths for appview v2 * tidy, docs fix --------- Co-authored-by: Devin Ivy Co-authored-by: Foysal Ahamed --- .../workflows/build-and-push-bsky-aws.yaml | 1 - .../workflows/build-and-push-bsky-ghcr.yaml | 1 + .prettierignore | 1 + packages/bsky/bin/migration-create.ts | 10 +- packages/bsky/build.js | 2 +- packages/bsky/package.json | 4 +- packages/bsky/proto/bsky.proto | 1164 ++ .../bsky/src/api/app/bsky/actor/getProfile.ts | 117 +- .../src/api/app/bsky/actor/getProfiles.ts | 72 +- .../src/api/app/bsky/actor/getSuggestions.ts | 178 +- .../src/api/app/bsky/actor/searchActors.ts | 139 +- .../app/bsky/actor/searchActorsTypeahead.ts | 133 +- .../src/api/app/bsky/feed/getActorFeeds.ts | 124 +- .../src/api/app/bsky/feed/getActorLikes.ts | 141 +- .../src/api/app/bsky/feed/getAuthorFeed.ts | 247 +- .../bsky/src/api/app/bsky/feed/getFeed.ts | 234 +- .../src/api/app/bsky/feed/getFeedGenerator.ts | 52 +- .../api/app/bsky/feed/getFeedGenerators.ts | 73 +- .../bsky/src/api/app/bsky/feed/getLikes.ts | 166 +- .../bsky/src/api/app/bsky/feed/getListFeed.ts | 149 +- .../src/api/app/bsky/feed/getPostThread.ts | 353 +- .../bsky/src/api/app/bsky/feed/getPosts.ts | 106 +- .../src/api/app/bsky/feed/getRepostedBy.ts | 143 +- .../api/app/bsky/feed/getSuggestedFeeds.ts | 32 +- .../bsky/src/api/app/bsky/feed/getTimeline.ts | 238 +- .../bsky/src/api/app/bsky/feed/searchPosts.ts | 146 +- .../bsky/src/api/app/bsky/graph/getBlocks.ts | 108 +- .../src/api/app/bsky/graph/getFollowers.ts | 170 +- .../bsky/src/api/app/bsky/graph/getFollows.ts | 181 +- .../bsky/src/api/app/bsky/graph/getList.ts | 143 +- .../src/api/app/bsky/graph/getListBlocks.ts | 111 +- .../src/api/app/bsky/graph/getListMutes.ts | 106 +- .../bsky/src/api/app/bsky/graph/getLists.ts | 110 +- .../bsky/src/api/app/bsky/graph/getMutes.ts | 109 +- .../api/app/bsky/graph/getRelationships.ts | 44 +- .../bsky/graph/getSuggestedFollowsByActor.ts | 193 +- .../bsky/src/api/app/bsky/graph/muteActor.ts | 47 +- .../src/api/app/bsky/graph/muteActorList.ts | 49 +- .../src/api/app/bsky/graph/unmuteActor.ts | 49 +- .../src/api/app/bsky/graph/unmuteActorList.ts | 39 +- .../app/bsky/notification/getUnreadCount.ts | 89 +- .../bsky/notification/listNotifications.ts | 224 +- .../api/app/bsky/notification/registerPush.ts | 54 +- .../api/app/bsky/notification/updateSeen.ts | 26 +- .../unspecced/getPopularFeedGenerators.ts | 108 +- .../bsky/unspecced/getTaggedSuggestions.ts | 11 +- packages/bsky/src/api/app/bsky/util/feed.ts | 19 - packages/bsky/src/api/blob-resolver.ts | 47 +- .../api/com/atproto/admin/getAccountInfos.ts | 18 +- .../api/com/atproto/admin/getSubjectStatus.ts | 54 +- .../com/atproto/admin/updateSubjectStatus.ts | 53 +- .../api/com/atproto/identity/resolveHandle.ts | 9 +- .../src/api/com/atproto/repo/getRecord.ts | 27 +- .../src/api/com/atproto/temp/fetchLabels.ts | 29 +- packages/bsky/src/api/health.ts | 4 +- packages/bsky/src/api/util.ts | 5 + packages/bsky/src/auth-verifier.ts | 77 +- packages/bsky/src/auto-moderator/hive.ts | 187 - packages/bsky/src/auto-moderator/index.ts | 91 - packages/bsky/src/auto-moderator/keyword.ts | 25 - packages/bsky/src/auto-moderator/util.ts | 138 - packages/bsky/src/config.ts | 317 +- packages/bsky/src/context.ts | 66 +- packages/bsky/src/daemon/config.ts | 60 - packages/bsky/src/daemon/context.ts | 27 - packages/bsky/src/daemon/index.ts | 78 - packages/bsky/src/daemon/logger.ts | 6 - packages/bsky/src/daemon/notifications.ts | 54 - packages/bsky/src/daemon/services.ts | 22 - packages/bsky/src/data-plane/bsync/index.ts | 98 + packages/bsky/src/data-plane/client.ts | 151 + packages/bsky/src/data-plane/index.ts | 3 + .../src/{ => data-plane/server}/background.ts | 10 +- .../server}/db/database-schema.ts | 2 + .../primary.ts => data-plane/server/db/db.ts} | 146 +- .../bsky/src/data-plane/server/db/index.ts | 1 + .../db/migrations/20230309T045948368Z-init.ts | 0 .../20230408T152211201Z-notification-init.ts | 0 .../20230417T210628672Z-moderation-init.ts | 0 .../20230420T211446071Z-did-cache.ts | 0 .../20230427T194702079Z-notif-record-index.ts | 0 .../20230605T144730094Z-post-profile-aggs.ts | 0 ...20230607T211442112Z-feed-generator-init.ts | 0 ...20230608T155101190Z-algo-whats-hot-view.ts | 0 .../20230608T201813132Z-mute-lists.ts | 0 .../migrations/20230608T205147239Z-mutes.ts | 0 .../migrations/20230609T153623961Z-blocks.ts | 0 ...30609T232122649Z-actor-deletion-indexes.ts | 0 .../20230610T203555962Z-suggested-follows.ts | 0 .../20230611T215300060Z-actor-state.ts | 0 .../20230620T161134972Z-post-langs.ts | 0 .../20230627T212437895Z-optional-handle.ts | 0 ...230629T220835893Z-remove-post-hierarchy.ts | 0 ...30703T045536691Z-feed-and-label-indices.ts | 0 .../20230720T164800037Z-posts-cursor-idx.ts | 0 ...1Z-feed-item-delete-invite-for-user-idx.ts | 0 .../20230808T172902639Z-repo-rev.ts | 0 .../20230810T203349843Z-action-duration.ts | 0 ...0230817T195936007Z-native-notifications.ts | 0 .../20230830T205507322Z-suggested-feeds.ts | 0 .../20230904T211011773Z-block-lists.ts | 0 .../20230906T222220386Z-thread-gating.ts | 0 .../20230920T213858047Z-add-tags-to-post.ts | 0 ...230929T192920807Z-record-cursor-indexes.ts | 0 ...33377Z-create-moderation-subject-status.ts | 0 .../20231220T225126090Z-blob-takedowns.ts | 0 .../20240124T023719200Z-tagged-suggestions.ts | 0 .../server}/db/migrations/index.ts | 1 - .../server}/db/migrations/provider.ts | 0 .../{ => data-plane/server}/db/pagination.ts | 27 +- .../server}/db/tables/actor-block.ts | 0 .../server}/db/tables/actor-state.ts | 0 .../server}/db/tables/actor-sync.ts | 0 .../server}/db/tables/actor.ts | 0 .../{ => data-plane/server}/db/tables/algo.ts | 0 .../server}/db/tables/blob-takedown.ts | 0 .../data-plane/server/db/tables/did-cache.ts | 13 + .../server}/db/tables/duplicate-record.ts | 0 .../server}/db/tables/feed-generator.ts | 0 .../server}/db/tables/feed-item.ts | 0 .../server}/db/tables/follow.ts | 0 .../server}/db/tables/label.ts | 0 .../{ => data-plane/server}/db/tables/like.ts | 0 .../server}/db/tables/list-block.ts | 0 .../server}/db/tables/list-item.ts | 0 .../server}/db/tables/list-mute.ts | 0 .../{ => data-plane/server}/db/tables/list.ts | 0 .../server}/db/tables/moderation.ts | 2 +- .../{ => data-plane/server}/db/tables/mute.ts | 0 .../db/tables/notification-push-token.ts | 0 .../server}/db/tables/notification.ts | 0 .../server}/db/tables/post-agg.ts | 0 .../server}/db/tables/post-embed.ts | 0 .../{ => data-plane/server}/db/tables/post.ts | 0 .../server}/db/tables/profile-agg.ts | 0 .../server}/db/tables/profile.ts | 0 .../server}/db/tables/record.ts | 0 .../server}/db/tables/repost.ts | 0 .../server}/db/tables/subscription.ts | 0 .../server}/db/tables/suggested-feed.ts | 0 .../server}/db/tables/suggested-follow.ts | 0 .../server}/db/tables/tagged-suggestion.ts | 0 .../server}/db/tables/thread-gate.ts | 0 .../server}/db/tables/view-param.ts | 0 .../src/{ => data-plane/server}/db/types.ts | 0 .../src/{ => data-plane/server}/db/util.ts | 0 packages/bsky/src/data-plane/server/index.ts | 36 + .../server}/indexing/index.ts | 71 +- .../server}/indexing/plugins/block.ts | 18 +- .../indexing/plugins/feed-generator.ts | 18 +- .../server}/indexing/plugins/follow.ts | 20 +- .../server}/indexing/plugins/like.ts | 20 +- .../server}/indexing/plugins/list-block.ts | 18 +- .../server}/indexing/plugins/list-item.ts | 20 +- .../server}/indexing/plugins/list.ts | 18 +- .../server}/indexing/plugins/post.ts | 48 +- .../server}/indexing/plugins/profile.ts | 18 +- .../server}/indexing/plugins/repost.ts | 20 +- .../server}/indexing/plugins/thread-gate.ts | 18 +- .../server}/indexing/processor.ts | 57 +- .../src/data-plane/server/routes/blocks.ts | 121 + .../src/data-plane/server/routes/feed-gens.ts | 70 + .../src/data-plane/server/routes/feeds.ts | 148 + .../src/data-plane/server/routes/follows.ts | 95 + .../src/data-plane/server/routes/identity.ts | 59 + .../src/data-plane/server/routes/index.ts | 55 + .../data-plane/server/routes/interactions.ts | 40 + .../src/data-plane/server/routes/labels.ts | 28 + .../src/data-plane/server/routes/likes.ts | 86 + .../src/data-plane/server/routes/lists.ts | 88 + .../data-plane/server/routes/moderation.ts | 102 + .../src/data-plane/server/routes/mutes.ts | 172 + .../src/data-plane/server/routes/notifs.ts | 115 + .../src/data-plane/server/routes/posts.ts | 21 + .../src/data-plane/server/routes/profile.ts | 49 + .../src/data-plane/server/routes/records.ts | 95 + .../data-plane/server/routes/relationships.ts | 148 + .../src/data-plane/server/routes/reposts.ts | 77 + .../src/data-plane/server/routes/search.ts | 61 + .../data-plane/server/routes/suggestions.ts | 175 + .../bsky/src/data-plane/server/routes/sync.ts | 16 + .../src/data-plane/server/routes/threads.ts | 33 + .../server/subscription/index.ts} | 286 +- .../server}/subscription/util.ts | 9 +- .../feed => data-plane/server}/util.ts | 124 +- packages/bsky/src/db/coordinator.ts | 107 - packages/bsky/src/db/db.ts | 91 - packages/bsky/src/db/index.ts | 3 - packages/bsky/src/db/leader.ts | 63 - .../20231205T000257238Z-remove-did-cache.ts | 14 - packages/bsky/src/db/views.ts | 50 - packages/bsky/src/did-cache.ts | 87 - packages/bsky/src/hydration/actor.ts | 149 + packages/bsky/src/hydration/feed.ts | 204 + packages/bsky/src/hydration/graph.ts | 195 + packages/bsky/src/hydration/hydrator.ts | 726 ++ packages/bsky/src/hydration/label.ts | 36 + packages/bsky/src/hydration/util.ts | 125 + packages/bsky/src/image/uri.ts | 11 +- packages/bsky/src/index.ts | 204 +- packages/bsky/src/indexer/config.ts | 271 - packages/bsky/src/indexer/context.ts | 68 - packages/bsky/src/indexer/index.ts | 166 - packages/bsky/src/indexer/logger.ts | 6 - packages/bsky/src/indexer/server.ts | 46 - packages/bsky/src/indexer/services.ts | 32 - packages/bsky/src/ingester/config.ts | 182 - packages/bsky/src/ingester/context.ts | 39 - packages/bsky/src/ingester/index.ts | 108 - .../bsky/src/ingester/label-subscription.ts | 76 - packages/bsky/src/ingester/logger.ts | 6 - .../bsky/src/ingester/mute-subscription.ts | 213 - packages/bsky/src/ingester/subscription.ts | 290 - packages/bsky/src/notifications.ts | 398 - packages/bsky/src/pipeline.ts | 58 +- packages/bsky/src/proto/bsky_connect.ts | 920 ++ packages/bsky/src/proto/bsky_pb.ts | 10619 ++++++++++++++++ packages/bsky/src/services/actor/index.ts | 242 - packages/bsky/src/services/actor/types.ts | 75 - packages/bsky/src/services/actor/views.ts | 375 - packages/bsky/src/services/feed/index.ts | 567 - packages/bsky/src/services/feed/types.ts | 107 - packages/bsky/src/services/feed/views.ts | 475 - packages/bsky/src/services/graph/index.ts | 404 - packages/bsky/src/services/graph/types.ts | 10 - packages/bsky/src/services/index.ts | 36 - packages/bsky/src/services/label/index.ts | 217 - .../bsky/src/services/moderation/index.ts | 118 - packages/bsky/src/services/types.ts | 4 - .../bsky/src/services/util/notification.ts | 70 - packages/bsky/src/services/util/post.ts | 65 - packages/bsky/src/services/util/search.ts | 172 - packages/bsky/src/views/index.ts | 854 ++ packages/bsky/src/views/types.ts | 72 + packages/bsky/src/views/util.ts | 64 + .../feed-generation.test.ts.snap | 61 +- packages/bsky/tests/_util.ts | 4 +- packages/bsky/tests/admin/admin-auth.test.ts | 29 +- packages/bsky/tests/admin/moderation.test.ts | 60 +- packages/bsky/tests/auth.test.ts | 3 +- .../fixtures/hiveai_resp_example.json | 401 - .../bsky/tests/auto-moderator/hive.test.ts | 16 - .../bsky/tests/auto-moderator/labeler.test.ts | 168 - packages/bsky/tests/blob-resolver.test.ts | 17 +- packages/bsky/tests/daemon.test.ts | 190 - .../__snapshots__/indexing.test.ts.snap | 16 +- .../bsky/tests/{ => data-plane}/db.test.ts | 85 +- .../duplicate-records.test.ts | 54 +- .../handle-invalidation.test.ts | 9 +- .../tests/{ => data-plane}/indexing.test.ts | 120 +- .../subscription/repo.test.ts | 28 +- .../subscription/util.test.ts | 4 +- packages/bsky/tests/did-cache.test.ts | 143 - packages/bsky/tests/feed-generation.test.ts | 59 +- packages/bsky/tests/image/server.test.ts | 1 - packages/bsky/tests/image/uri.test.ts | 18 +- .../bsky/tests/notification-server.test.ts | 270 - .../bsky/tests/pipeline/backpressure.test.ts | 68 - packages/bsky/tests/pipeline/reingest.test.ts | 55 - .../bsky/tests/pipeline/repartition.test.ts | 86 - packages/bsky/tests/reprocessing.test.ts | 70 - packages/bsky/tests/server.test.ts | 19 +- .../bsky/tests/subscription/mutes.test.ts | 170 - .../__snapshots__/author-feed.test.ts.snap | 78 +- .../__snapshots__/block-lists.test.ts.snap | 8 +- .../views/__snapshots__/blocks.test.ts.snap | 6 +- .../views/__snapshots__/follows.test.ts.snap | 25 +- .../views/__snapshots__/likes.test.ts.snap | 4 +- .../__snapshots__/list-feed.test.ts.snap | 14 +- .../__snapshots__/mute-lists.test.ts.snap | 12 +- .../views/__snapshots__/mutes.test.ts.snap | 4 +- .../__snapshots__/notifications.test.ts.snap | 38 +- .../views/__snapshots__/thread.test.ts.snap | 39 +- .../views/__snapshots__/timeline.test.ts.snap | 272 +- .../bsky/tests/views/actor-search.test.ts | 26 +- packages/bsky/tests/views/author-feed.test.ts | 77 +- packages/bsky/tests/views/blocks.test.ts | 14 +- packages/bsky/tests/views/follows.test.ts | 79 +- packages/bsky/tests/views/list-feed.test.ts | 76 +- packages/bsky/tests/views/mute-lists.test.ts | 2 - packages/bsky/tests/views/mutes.test.ts | 2 +- .../bsky/tests/views/notifications.test.ts | 48 +- packages/bsky/tests/views/profile.test.ts | 87 +- .../tests/views/suggested-follows.test.ts | 6 +- packages/bsky/tests/views/suggestions.test.ts | 17 +- packages/bsky/tests/views/thread.test.ts | 283 +- .../bsky/tests/views/threadgating.test.ts | 10 +- packages/bsky/tests/views/timeline.test.ts | 145 +- packages/bsky/tsconfig.json | 3 +- packages/common-web/src/arrays.ts | 7 + packages/dev-env/src/bsky.ts | 330 +- packages/dev-env/src/mock/index.ts | 50 +- packages/dev-env/src/network-no-appview.ts | 2 +- packages/dev-env/src/network.ts | 16 +- packages/dev-env/src/seed/basic.ts | 46 +- packages/dev-env/src/seed/client.ts | 6 +- packages/dev-env/src/types.ts | 8 +- packages/dev-env/src/util.ts | 2 +- packages/identity/src/did/atproto-data.ts | 6 + .../moderation-events.test.ts.snap | 128 +- .../__snapshots__/moderation.test.ts.snap | 8 +- .../ozone/tests/moderation-events.test.ts | 14 +- .../ozone/tests/moderation-statuses.test.ts | 4 +- packages/ozone/tests/moderation.test.ts | 25 +- packages/ozone/tests/repo-search.test.ts | 1 - packages/pds/tests/_util.ts | 6 +- .../__snapshots__/feedgen.test.ts.snap | 12 +- .../proxied/__snapshots__/views.test.ts.snap | 322 +- packages/pds/tests/proxied/admin.test.ts | 5 +- packages/pds/tests/proxied/feedgen.test.ts | 3 +- packages/pds/tests/proxied/procedures.test.ts | 10 +- .../tests/proxied/read-after-write.test.ts | 2 +- packages/pds/tests/proxied/views.test.ts | 10 +- packages/pds/tests/seeds/basic.ts | 40 +- pnpm-lock.yaml | 84 +- services/bsky/Dockerfile | 5 +- services/bsky/README.md | 23 + services/bsky/api.js | 209 +- services/bsky/daemon.js | 47 - services/bsky/indexer.js | 127 - services/bsky/ingester.js | 75 - services/bsky/package.json | 2 +- 322 files changed, 21332 insertions(+), 13929 deletions(-) create mode 100644 packages/bsky/proto/bsky.proto delete mode 100644 packages/bsky/src/api/app/bsky/util/feed.ts delete mode 100644 packages/bsky/src/auto-moderator/hive.ts delete mode 100644 packages/bsky/src/auto-moderator/index.ts delete mode 100644 packages/bsky/src/auto-moderator/keyword.ts delete mode 100644 packages/bsky/src/auto-moderator/util.ts delete mode 100644 packages/bsky/src/daemon/config.ts delete mode 100644 packages/bsky/src/daemon/context.ts delete mode 100644 packages/bsky/src/daemon/index.ts delete mode 100644 packages/bsky/src/daemon/logger.ts delete mode 100644 packages/bsky/src/daemon/notifications.ts delete mode 100644 packages/bsky/src/daemon/services.ts create mode 100644 packages/bsky/src/data-plane/bsync/index.ts create mode 100644 packages/bsky/src/data-plane/client.ts create mode 100644 packages/bsky/src/data-plane/index.ts rename packages/bsky/src/{ => data-plane/server}/background.ts (76%) rename packages/bsky/src/{ => data-plane/server}/db/database-schema.ts (97%) rename packages/bsky/src/{db/primary.ts => data-plane/server/db/db.ts} (55%) create mode 100644 packages/bsky/src/data-plane/server/db/index.ts rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230309T045948368Z-init.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230408T152211201Z-notification-init.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230417T210628672Z-moderation-init.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230420T211446071Z-did-cache.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230427T194702079Z-notif-record-index.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230605T144730094Z-post-profile-aggs.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230607T211442112Z-feed-generator-init.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230608T155101190Z-algo-whats-hot-view.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230608T201813132Z-mute-lists.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230608T205147239Z-mutes.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230609T153623961Z-blocks.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230609T232122649Z-actor-deletion-indexes.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230610T203555962Z-suggested-follows.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230611T215300060Z-actor-state.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230620T161134972Z-post-langs.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230627T212437895Z-optional-handle.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230629T220835893Z-remove-post-hierarchy.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230703T045536691Z-feed-and-label-indices.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230720T164800037Z-posts-cursor-idx.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230807T035309811Z-feed-item-delete-invite-for-user-idx.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230808T172902639Z-repo-rev.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230810T203349843Z-action-duration.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230817T195936007Z-native-notifications.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230830T205507322Z-suggested-feeds.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230904T211011773Z-block-lists.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230906T222220386Z-thread-gating.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230920T213858047Z-add-tags-to-post.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20230929T192920807Z-record-cursor-indexes.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20231003T202833377Z-create-moderation-subject-status.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20231220T225126090Z-blob-takedowns.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/20240124T023719200Z-tagged-suggestions.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/index.ts (97%) rename packages/bsky/src/{ => data-plane/server}/db/migrations/provider.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/pagination.ts (88%) rename packages/bsky/src/{ => data-plane/server}/db/tables/actor-block.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/actor-state.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/actor-sync.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/actor.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/algo.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/blob-takedown.ts (100%) create mode 100644 packages/bsky/src/data-plane/server/db/tables/did-cache.ts rename packages/bsky/src/{ => data-plane/server}/db/tables/duplicate-record.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/feed-generator.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/feed-item.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/follow.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/label.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/like.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/list-block.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/list-item.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/list-mute.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/list.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/moderation.ts (96%) rename packages/bsky/src/{ => data-plane/server}/db/tables/mute.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/notification-push-token.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/notification.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/post-agg.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/post-embed.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/post.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/profile-agg.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/profile.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/record.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/repost.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/subscription.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/suggested-feed.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/suggested-follow.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/tagged-suggestion.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/thread-gate.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/tables/view-param.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/types.ts (100%) rename packages/bsky/src/{ => data-plane/server}/db/util.ts (100%) create mode 100644 packages/bsky/src/data-plane/server/index.ts rename packages/bsky/src/{services => data-plane/server}/indexing/index.ts (86%) rename packages/bsky/src/{services => data-plane/server}/indexing/plugins/block.ts (77%) rename packages/bsky/src/{services => data-plane/server}/indexing/plugins/feed-generator.ts (77%) rename packages/bsky/src/{services => data-plane/server}/indexing/plugins/follow.ts (84%) rename packages/bsky/src/{services => data-plane/server}/indexing/plugins/like.ts (83%) rename packages/bsky/src/{services => data-plane/server}/indexing/plugins/list-block.ts (77%) rename packages/bsky/src/{services => data-plane/server}/indexing/plugins/list-item.ts (79%) rename packages/bsky/src/{services => data-plane/server}/indexing/plugins/list.ts (77%) rename packages/bsky/src/{services => data-plane/server}/indexing/plugins/post.ts (89%) rename packages/bsky/src/{services => data-plane/server}/indexing/plugins/profile.ts (75%) rename packages/bsky/src/{services => data-plane/server}/indexing/plugins/repost.ts (85%) rename packages/bsky/src/{services => data-plane/server}/indexing/plugins/thread-gate.ts (79%) rename packages/bsky/src/{services => data-plane/server}/indexing/processor.ts (80%) create mode 100644 packages/bsky/src/data-plane/server/routes/blocks.ts create mode 100644 packages/bsky/src/data-plane/server/routes/feed-gens.ts create mode 100644 packages/bsky/src/data-plane/server/routes/feeds.ts create mode 100644 packages/bsky/src/data-plane/server/routes/follows.ts create mode 100644 packages/bsky/src/data-plane/server/routes/identity.ts create mode 100644 packages/bsky/src/data-plane/server/routes/index.ts create mode 100644 packages/bsky/src/data-plane/server/routes/interactions.ts create mode 100644 packages/bsky/src/data-plane/server/routes/labels.ts create mode 100644 packages/bsky/src/data-plane/server/routes/likes.ts create mode 100644 packages/bsky/src/data-plane/server/routes/lists.ts create mode 100644 packages/bsky/src/data-plane/server/routes/moderation.ts create mode 100644 packages/bsky/src/data-plane/server/routes/mutes.ts create mode 100644 packages/bsky/src/data-plane/server/routes/notifs.ts create mode 100644 packages/bsky/src/data-plane/server/routes/posts.ts create mode 100644 packages/bsky/src/data-plane/server/routes/profile.ts create mode 100644 packages/bsky/src/data-plane/server/routes/records.ts create mode 100644 packages/bsky/src/data-plane/server/routes/relationships.ts create mode 100644 packages/bsky/src/data-plane/server/routes/reposts.ts create mode 100644 packages/bsky/src/data-plane/server/routes/search.ts create mode 100644 packages/bsky/src/data-plane/server/routes/suggestions.ts create mode 100644 packages/bsky/src/data-plane/server/routes/sync.ts create mode 100644 packages/bsky/src/data-plane/server/routes/threads.ts rename packages/bsky/src/{indexer/subscription.ts => data-plane/server/subscription/index.ts} (57%) rename packages/bsky/src/{ => data-plane/server}/subscription/util.ts (93%) rename packages/bsky/src/{services/feed => data-plane/server}/util.ts (55%) delete mode 100644 packages/bsky/src/db/coordinator.ts delete mode 100644 packages/bsky/src/db/db.ts delete mode 100644 packages/bsky/src/db/index.ts delete mode 100644 packages/bsky/src/db/leader.ts delete mode 100644 packages/bsky/src/db/migrations/20231205T000257238Z-remove-did-cache.ts delete mode 100644 packages/bsky/src/db/views.ts delete mode 100644 packages/bsky/src/did-cache.ts create mode 100644 packages/bsky/src/hydration/actor.ts create mode 100644 packages/bsky/src/hydration/feed.ts create mode 100644 packages/bsky/src/hydration/graph.ts create mode 100644 packages/bsky/src/hydration/hydrator.ts create mode 100644 packages/bsky/src/hydration/label.ts create mode 100644 packages/bsky/src/hydration/util.ts delete mode 100644 packages/bsky/src/indexer/config.ts delete mode 100644 packages/bsky/src/indexer/context.ts delete mode 100644 packages/bsky/src/indexer/index.ts delete mode 100644 packages/bsky/src/indexer/logger.ts delete mode 100644 packages/bsky/src/indexer/server.ts delete mode 100644 packages/bsky/src/indexer/services.ts delete mode 100644 packages/bsky/src/ingester/config.ts delete mode 100644 packages/bsky/src/ingester/context.ts delete mode 100644 packages/bsky/src/ingester/index.ts delete mode 100644 packages/bsky/src/ingester/label-subscription.ts delete mode 100644 packages/bsky/src/ingester/logger.ts delete mode 100644 packages/bsky/src/ingester/mute-subscription.ts delete mode 100644 packages/bsky/src/ingester/subscription.ts delete mode 100644 packages/bsky/src/notifications.ts create mode 100644 packages/bsky/src/proto/bsky_connect.ts create mode 100644 packages/bsky/src/proto/bsky_pb.ts delete mode 100644 packages/bsky/src/services/actor/index.ts delete mode 100644 packages/bsky/src/services/actor/types.ts delete mode 100644 packages/bsky/src/services/actor/views.ts delete mode 100644 packages/bsky/src/services/feed/index.ts delete mode 100644 packages/bsky/src/services/feed/types.ts delete mode 100644 packages/bsky/src/services/feed/views.ts delete mode 100644 packages/bsky/src/services/graph/index.ts delete mode 100644 packages/bsky/src/services/graph/types.ts delete mode 100644 packages/bsky/src/services/index.ts delete mode 100644 packages/bsky/src/services/label/index.ts delete mode 100644 packages/bsky/src/services/moderation/index.ts delete mode 100644 packages/bsky/src/services/types.ts delete mode 100644 packages/bsky/src/services/util/notification.ts delete mode 100644 packages/bsky/src/services/util/post.ts delete mode 100644 packages/bsky/src/services/util/search.ts create mode 100644 packages/bsky/src/views/index.ts create mode 100644 packages/bsky/src/views/types.ts create mode 100644 packages/bsky/src/views/util.ts delete mode 100644 packages/bsky/tests/auto-moderator/fixtures/hiveai_resp_example.json delete mode 100644 packages/bsky/tests/auto-moderator/hive.test.ts delete mode 100644 packages/bsky/tests/auto-moderator/labeler.test.ts delete mode 100644 packages/bsky/tests/daemon.test.ts rename packages/bsky/tests/{ => data-plane}/__snapshots__/indexing.test.ts.snap (98%) rename packages/bsky/tests/{ => data-plane}/db.test.ts (65%) rename packages/bsky/tests/{ => data-plane}/duplicate-records.test.ts (75%) rename packages/bsky/tests/{ => data-plane}/handle-invalidation.test.ts (94%) rename packages/bsky/tests/{ => data-plane}/indexing.test.ts (84%) rename packages/bsky/tests/{ => data-plane}/subscription/repo.test.ts (83%) rename packages/bsky/tests/{ => data-plane}/subscription/util.test.ts (98%) delete mode 100644 packages/bsky/tests/did-cache.test.ts delete mode 100644 packages/bsky/tests/notification-server.test.ts delete mode 100644 packages/bsky/tests/pipeline/backpressure.test.ts delete mode 100644 packages/bsky/tests/pipeline/reingest.test.ts delete mode 100644 packages/bsky/tests/pipeline/repartition.test.ts delete mode 100644 packages/bsky/tests/reprocessing.test.ts delete mode 100644 packages/bsky/tests/subscription/mutes.test.ts create mode 100644 services/bsky/README.md delete mode 100644 services/bsky/daemon.js delete mode 100644 services/bsky/indexer.js delete mode 100644 services/bsky/ingester.js diff --git a/.github/workflows/build-and-push-bsky-aws.yaml b/.github/workflows/build-and-push-bsky-aws.yaml index f534e015ea5..36b1aa23cb3 100644 --- a/.github/workflows/build-and-push-bsky-aws.yaml +++ b/.github/workflows/build-and-push-bsky-aws.yaml @@ -3,7 +3,6 @@ on: push: branches: - main - - appview-v1-courier env: REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }} USERNAME: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_USERNAME }} diff --git a/.github/workflows/build-and-push-bsky-ghcr.yaml b/.github/workflows/build-and-push-bsky-ghcr.yaml index 5d22cd9a389..f1bf0bd10f5 100644 --- a/.github/workflows/build-and-push-bsky-ghcr.yaml +++ b/.github/workflows/build-and-push-bsky-ghcr.yaml @@ -3,6 +3,7 @@ on: push: branches: - main + - appview-v2 env: REGISTRY: ghcr.io USERNAME: ${{ github.actor }} diff --git a/.prettierignore b/.prettierignore index 6340cc359c2..7bb137f13ea 100644 --- a/.prettierignore +++ b/.prettierignore @@ -8,3 +8,4 @@ pnpm-lock.yaml .pnpm* .changeset *.d.ts +packages/bsky/src/data-plane/gen \ No newline at end of file diff --git a/packages/bsky/bin/migration-create.ts b/packages/bsky/bin/migration-create.ts index b51c536c4f2..27edc5e0979 100644 --- a/packages/bsky/bin/migration-create.ts +++ b/packages/bsky/bin/migration-create.ts @@ -14,7 +14,15 @@ export async function main() { ) } const filename = `${prefix}-${name}` - const dir = path.join(__dirname, '..', 'src', 'db', 'migrations') + const dir = path.join( + __dirname, + '..', + 'src', + 'data-plane', + 'server', + 'db', + 'migrations', + ) await fs.writeFile(path.join(dir, `${filename}.ts`), template, { flag: 'wx' }) await fs.writeFile( diff --git a/packages/bsky/build.js b/packages/bsky/build.js index 3822d9bc98f..85c4a88243b 100644 --- a/packages/bsky/build.js +++ b/packages/bsky/build.js @@ -5,7 +5,7 @@ const buildShallow = require('esbuild').build({ logLevel: 'info', - entryPoints: ['src/index.ts', 'src/db/index.ts'], + entryPoints: ['src/index.ts'], bundle: true, sourcemap: true, outdir: 'dist', diff --git a/packages/bsky/package.json b/packages/bsky/package.json index ec6f8f1feb3..099501296ed 100644 --- a/packages/bsky/package.json +++ b/packages/bsky/package.json @@ -42,6 +42,7 @@ "@atproto/xrpc-server": "workspace:^", "@bufbuild/protobuf": "^1.5.0", "@connectrpc/connect": "^1.1.4", + "@connectrpc/connect-express": "^1.1.4", "@connectrpc/connect-node": "^1.1.4", "@did-plc/lib": "^0.0.1", "@isaacs/ttlcache": "^1.4.1", @@ -80,6 +81,7 @@ "@types/express-serve-static-core": "^4.17.36", "@types/pg": "^8.6.6", "@types/qs": "^6.9.7", - "axios": "^0.27.2" + "axios": "^0.27.2", + "http2-express-bridge": "^1.0.7" } } diff --git a/packages/bsky/proto/bsky.proto b/packages/bsky/proto/bsky.proto new file mode 100644 index 00000000000..ffff8efa043 --- /dev/null +++ b/packages/bsky/proto/bsky.proto @@ -0,0 +1,1164 @@ +syntax = "proto3"; + +package bsky; +option go_package = "./;bsky"; + +import "google/protobuf/timestamp.proto"; + +// +// Read Path +// + +message Record { + bytes record = 1; + string cid = 2; + google.protobuf.Timestamp indexed_at = 4; + bool taken_down = 5; + google.protobuf.Timestamp created_at = 6; + google.protobuf.Timestamp sorted_at = 7; + string takedown_ref = 8; +} + +message GetBlockRecordsRequest { + repeated string uris = 1; +} + +message GetBlockRecordsResponse { + repeated Record records = 1; +} + +message GetFeedGeneratorRecordsRequest { + repeated string uris = 1; +} + +message GetFeedGeneratorRecordsResponse { + repeated Record records = 1; +} + +message GetFollowRecordsRequest { + repeated string uris = 1; +} + +message GetFollowRecordsResponse { + repeated Record records = 1; +} + +message GetLikeRecordsRequest { + repeated string uris = 1; +} + +message GetLikeRecordsResponse { + repeated Record records = 1; +} + +message GetListBlockRecordsRequest { + repeated string uris = 1; +} + +message GetListBlockRecordsResponse { + repeated Record records = 1; +} + +message GetListItemRecordsRequest { + repeated string uris = 1; +} + +message GetListItemRecordsResponse { + repeated Record records = 1; +} + +message GetListRecordsRequest { + repeated string uris = 1; +} + +message GetListRecordsResponse { + repeated Record records = 1; +} + +message PostRecordMeta { + bool violates_thread_gate = 1; + bool has_media = 2; + bool is_reply = 3; +} + +message GetPostRecordsRequest { + repeated string uris = 1; +} + +message GetPostRecordsResponse { + repeated Record records = 1; + repeated PostRecordMeta meta = 2; +} + +message GetProfileRecordsRequest { + repeated string uris = 1; +} + +message GetProfileRecordsResponse { + repeated Record records = 1; +} + +message GetRepostRecordsRequest { + repeated string uris = 1; +} + +message GetRepostRecordsResponse { + repeated Record records = 1; +} + +message GetThreadGateRecordsRequest { + repeated string uris = 1; +} + +message GetThreadGateRecordsResponse { + repeated Record records = 1; +} + + +// +// Follows +// + +// - Return follow uris where user A follows users B, C, D, … +// - E.g. for viewer state on `getProfiles` +message GetActorFollowsActorsRequest { + string actor_did = 1; + repeated string target_dids = 2; +} + +message GetActorFollowsActorsResponse { + repeated string uris = 1; +} + +// - Return follow uris of users who follows user A +// - For `getFollowers` list +message GetFollowersRequest { + string actor_did = 1; + int32 limit = 2; + string cursor = 3; +} + +message FollowInfo { + string uri = 1; + string actor_did = 2; + string subject_did = 3; +} + +message GetFollowersResponse { + repeated FollowInfo followers = 1; + string cursor = 2; +} + +// - Return follow uris of users A follows +// - For `getFollows` list +message GetFollowsRequest { + string actor_did = 1; + int32 limit = 2; + string cursor = 3; +} + +message GetFollowsResponse { + repeated FollowInfo follows = 1; + string cursor = 2; +} + +// +// Likes +// + +// - return like uris where subject uri is subject A +// - `getLikes` list for a post +message GetLikesBySubjectRequest { + RecordRef subject = 1; + int32 limit = 2; + string cursor = 3; +} + +message GetLikesBySubjectResponse { + repeated string uris = 1; + string cursor = 2; +} + +// - return like uris for user A on subject B, C, D... +// - viewer state on posts +message GetLikesByActorAndSubjectsRequest { + string actor_did = 1; + repeated RecordRef refs = 2; +} + +message GetLikesByActorAndSubjectsResponse { + repeated string uris = 1; +} + +// - return recent like uris for user A +// - `getActorLikes` list for a user +message GetActorLikesRequest { + string actor_did = 1; + int32 limit = 2; + string cursor = 3; +} + +message LikeInfo { + string uri = 1; + string subject = 2; +} + +message GetActorLikesResponse { + repeated LikeInfo likes = 1; + string cursor = 2; +} + +// +// Interactions +// +message GetInteractionCountsRequest { + repeated RecordRef refs = 1; +} + +message GetInteractionCountsResponse { + repeated int32 likes = 1; + repeated int32 reposts = 2; + repeated int32 replies = 3; +} + +message GetCountsForUsersRequest { + repeated string dids = 1; +} + +message GetCountsForUsersResponse { + repeated int32 posts = 1; + repeated int32 reposts = 2; + repeated int32 following = 3; + repeated int32 followers = 4; +} + +// +// Reposts +// + +// - return repost uris where subject uri is subject A +// - `getReposts` list for a post +message GetRepostsBySubjectRequest { + RecordRef subject = 1; + int32 limit = 2; + string cursor = 3; +} + +message GetRepostsBySubjectResponse { + repeated string uris = 1; + string cursor = 2; +} + +// - return repost uris for user A on subject B, C, D... +// - viewer state on posts +message GetRepostsByActorAndSubjectsRequest { + string actor_did = 1; + repeated RecordRef refs = 2; +} + +message RecordRef { + string uri = 1; + string cid = 2; +} + +message GetRepostsByActorAndSubjectsResponse { + repeated string uris = 1; +} + +// - return recent repost uris for user A +// - `getActorReposts` list for a user +message GetActorRepostsRequest { + string actor_did = 1; + int32 limit = 2; + string cursor = 3; +} + +message GetActorRepostsResponse { + repeated string uris = 1; + string cursor = 2; +} + +// +// Profile +// + +// - return actor information for dids A, B, C… +// - profile hydration +// - should this include handles? apply repo takedown? +message GetActorsRequest { + repeated string dids = 1; +} + +message ActorInfo { + bool exists = 1; + string handle = 2; + Record profile = 3; + bool taken_down = 4; + string takedown_ref = 5; + google.protobuf.Timestamp tombstoned_at = 6; +} + +message GetActorsResponse { + repeated ActorInfo actors = 1; +} + +// - return did for handle A +// - `resolveHandle` +// - answering queries where the query param is a handle +message GetDidsByHandlesRequest { + repeated string handles = 1; +} + +message GetDidsByHandlesResponse { + repeated string dids = 1; +} + +// +// Relationships +// + +// - return relationships between user A and users B, C, D... +// - profile hydration +// - block application +message GetRelationshipsRequest { + string actor_did = 1; + repeated string target_dids = 2; +} + +message Relationships { + bool muted = 1; + string muted_by_list = 2; + string blocked_by = 3; + string blocking = 4; + string blocked_by_list = 5; + string blocking_by_list = 6; + string following = 7; + string followed_by = 8; +} + +message GetRelationshipsResponse { + repeated Relationships relationships = 1; +} + +// - return whether a block (bidrectionally and either direct or through a list) exists between two dids +// - enforcing 3rd party block violations +message RelationshipPair { + string a = 1; + string b = 2; +} + +message GetBlockExistenceRequest { + repeated RelationshipPair pairs = 1; +} + +message GetBlockExistenceResponse { + repeated bool exists = 1; +} + + +// +// Lists +// + +message ListItemInfo { + string uri = 1; + string did = 2; +} + +// - Return dids of users in list A +// - E.g. to view items in one of your mute lists +message GetListMembersRequest { + string list_uri = 1; + int32 limit = 2; + string cursor = 3; +} + +message GetListMembersResponse { + repeated ListItemInfo listitems = 1; + string cursor = 2; +} + +// - Return list uris where user A in list B, C, D… +// - Used in thread reply gates +message GetListMembershipRequest { + string actor_did = 1; + repeated string list_uris = 2; +} + +message GetListMembershipResponse { + repeated string listitem_uris = 1; +} + +// - Return number of items in list A +// - For aggregate +message GetListCountRequest { + string list_uri = 1; +} + +message GetListCountResponse { + int32 count = 1; +} + + +// - return list of uris of lists created by A +// - `getLists` +message GetActorListsRequest { + string actor_did = 1; + int32 limit = 2; + string cursor = 3; +} + +message GetActorListsResponse { + repeated string list_uris = 1; + string cursor = 2; +} + +// +// Mutes +// + +// - return boolean if user A has muted user B +// - hydrating mute state onto profiles +message GetActorMutesActorRequest { + string actor_did = 1; + string target_did = 2; +} + +message GetActorMutesActorResponse { + bool muted = 1; +} + +// - return list of user dids of users who A mutes +// - `getMutes` +message GetMutesRequest { + string actor_did = 1; + int32 limit = 2; + string cursor = 3; +} + +message GetMutesResponse { + repeated string dids = 1; + string cursor = 2; +} + +// +// Mutelists +// + +// - return list uri of *any* list through which user A has muted user B +// - hydrating mute state onto profiles +// - note: we only need *one* uri even if a user is muted by multiple lists +message GetActorMutesActorViaListRequest { + string actor_did = 1; + string target_did = 2; +} + +message GetActorMutesActorViaListResponse { + string list_uri = 1; +} + +// - return boolean if actor A has subscribed to mutelist B +// - list view hydration +message GetMutelistSubscriptionRequest { + string actor_did = 1; + string list_uri = 2; +} + +message GetMutelistSubscriptionResponse { + bool subscribed = 1; +} + +// - return list of list uris of mutelists that A subscribes to +// - `getListMutes` +message GetMutelistSubscriptionsRequest { + string actor_did = 1; + int32 limit = 2; + string cursor = 3; +} + +message GetMutelistSubscriptionsResponse { + repeated string list_uris = 1; + string cursor = 2; +} + +// +// Blocks +// + +// - Return block uri if there is a block between users A & B (bidirectional) +// - hydrating (& actioning) block state on profiles +// - handling 3rd party blocks +message GetBidirectionalBlockRequest { + string actor_did = 1; + string target_did = 2; +} + +message GetBidirectionalBlockResponse { + string block_uri = 1; +} + +// - Return list of block uris and user dids of users who A blocks +// - `getBlocks` +message GetBlocksRequest { + string actor_did = 1; + int32 limit = 2; + string cursor = 3; +} + +message GetBlocksResponse { + repeated string block_uris = 1; + string cursor = 2; +} + +// +// Blocklists +// + +// - Return list uri of ***any*** list through which users A & B have a block (bidirectional) +// - hydrating (& actioning) block state on profiles +// - handling 3rd party blocks +message GetBidirectionalBlockViaListRequest { + string actor_did = 1; + string target_did = 2; +} + +message GetBidirectionalBlockViaListResponse { + string list_uri = 1; +} + +// - return boolean if user A has subscribed to blocklist B +// - list view hydration +message GetBlocklistSubscriptionRequest { + string actor_did = 1; + string list_uri = 2; +} + +message GetBlocklistSubscriptionResponse { + string listblock_uri = 1; +} + +// - return list of list uris of Blockslists that A subscribes to +// - `getListBlocks` +message GetBlocklistSubscriptionsRequest { + string actor_did = 1; + int32 limit = 2; + string cursor = 3; +} + +message GetBlocklistSubscriptionsResponse { + repeated string list_uris = 1; + string cursor = 2; +} + +// +// Notifications +// + +// - list recent notifications for a user +// - notifications should include a uri for the record that caused the notif & a “reason” for the notification (reply, like, quotepost, etc) +// - this should include both read & unread notifs +message GetNotificationsRequest { + string actor_did = 1; + int32 limit = 2; + string cursor = 3; +} + +message Notification { + string recipient_did = 1; + string uri = 2; + string reason = 3; + string reason_subject = 4; + google.protobuf.Timestamp timestamp = 5; +} + +message GetNotificationsResponse { + repeated Notification notifications = 1; + string cursor = 2; +} + +// - update a user’s “last seen time” +// - `updateSeen` +message UpdateNotificationSeenRequest { + string actor_did = 1; + google.protobuf.Timestamp timestamp = 2; +} + +message UpdateNotificationSeenResponse {} + +// - get a user’s “last seen time” +// - hydrating read state onto notifications +message GetNotificationSeenRequest { + string actor_did = 1; +} + +message GetNotificationSeenResponse { + google.protobuf.Timestamp timestamp = 1; +} + +// - get a count of all unread notifications (notifications after `updateSeen`) +// - `getUnreadCount` +message GetUnreadNotificationCountRequest { + string actor_did = 1; +} + +message GetUnreadNotificationCountResponse { + int32 count = 1; +} + +// +// FeedGenerators +// + +// - Return uris of feed generator records created by user A +// - `getActorFeeds` +message GetActorFeedsRequest { + string actor_did = 1; + int32 limit = 2; + string cursor = 3; +} + +message GetActorFeedsResponse { + repeated string uris = 1; + string cursor = 2; +} + +// - Returns a list of suggested feed generator uris for an actor, paginated +// - `getSuggestedFeeds` +// - This is currently just hardcoded in the Appview DB +message GetSuggestedFeedsRequest { + string actor_did = 1; + int32 limit = 2; + string cursor = 3; +} + +message GetSuggestedFeedsResponse { + repeated string uris = 1; + string cursor = 2; +} + +message SearchFeedGeneratorsRequest { + string query = 1; + int32 limit = 2; +} + +message SearchFeedGeneratorsResponse { + repeated string uris = 1; +} + +// - Returns feed generator validity and online status with uris A, B, C… +// - Not currently being used, but could be worhthwhile. +message GetFeedGeneratorStatusRequest { + repeated string uris = 1; +} + +message GetFeedGeneratorStatusResponse { + repeated string status = 1; +} + +// +// Feeds +// + +enum FeedType { + FEED_TYPE_UNSPECIFIED = 0; + FEED_TYPE_POSTS_AND_AUTHOR_THREADS = 1; + FEED_TYPE_POSTS_NO_REPLIES = 2; + FEED_TYPE_POSTS_WITH_MEDIA = 3; +} + +// - Returns recent posts authored by a given DID, paginated +// - `getAuthorFeed` +// - Optionally: filter by if a post is/isn’t a reply and if a post has a media object in it +message GetAuthorFeedRequest { + string actor_did = 1; + int32 limit = 2; + string cursor = 3; + FeedType feed_type = 4; +} + +message AuthorFeedItem { + string uri = 1; + string cid = 2; + string repost = 3; + string repost_cid = 4; + bool posts_and_author_threads = 5; + bool posts_no_replies = 6; + bool posts_with_media = 7; + bool is_reply = 8; + bool is_repost = 9; + bool is_quote_post = 10; +} + +message GetAuthorFeedResponse { + repeated AuthorFeedItem items = 1; + string cursor = 2; +} + +// - Returns recent posts authored by users followed by a given DID, paginated +// - `getTimeline` +message GetTimelineRequest { + string actor_did = 1; + int32 limit = 2; + string cursor = 3; + bool exclude_replies = 4; + bool exclude_reposts = 5; + bool exclude_quotes = 6; +} + +message GetTimelineResponse { + repeated TimelineFeedItem items = 1; + string cursor = 2; +} + +message TimelineFeedItem { + string uri = 1; + string cid = 2; + string repost = 3; + string repost_cid = 4; + bool is_reply = 5; + bool is_repost = 6; + bool is_quote_post = 7; +} + +// - Return recent post uris from users in list A +// - `getListFeed` +// - (This is essentially the same as `getTimeline` but instead of follows of a did, it is list items of a list) +message GetListFeedRequest { + string list_uri = 1; + int32 limit = 2; + string cursor = 3; + bool exclude_replies = 4; + bool exclude_reposts = 5; + bool exclude_quotes = 6; +} + +message GetListFeedResponse { + repeated TimelineFeedItem items = 1; + string cursor = 2; +} + +// +// Threads +// + +// Return posts uris of any replies N levels above or M levels below post A +message GetThreadRequest { + string post_uri = 1; + int32 above = 2; + int32 below = 3; +} + +message GetThreadResponse { + repeated string uris = 1; +} + +// +// Search +// + +// - Return DIDs of actors matching term, paginated +// - `searchActors` skeleton +message SearchActorsRequest { + string term = 1; + int32 limit = 2; + string cursor = 3; +} + +message SearchActorsResponse { + repeated string dids = 1; + string cursor = 2; +} + +// - Return uris of posts matching term, paginated +// - `searchPosts` skeleton +message SearchPostsRequest { + string term = 1; + int32 limit = 2; + string cursor = 3; +} + +message SearchPostsResponse { + repeated string uris = 1; + string cursor = 2; +} + +// +// Suggestions +// + +// - Return DIDs of suggested follows for a user, excluding anyone they already follow +// - `getSuggestions`, `getSuggestedFollowsByActor` +message GetFollowSuggestionsRequest { + string actor_did = 1; + string relative_to_did = 2; + int32 limit = 3; + string cursor = 4; +} + +message GetFollowSuggestionsResponse { + repeated string dids = 1; + string cursor = 2; +} + +message SuggestedEntity { + string tag = 1; + string subject = 2; + string subject_type = 3; + int64 priority = 4; +} + +message GetSuggestedEntitiesRequest { + int32 limit = 1; + string cursor = 2; +} + +message GetSuggestedEntitiesResponse { + repeated SuggestedEntity entities = 1; + string cursor = 2; +} + +// +// Posts +// + +// - Return post reply count with uris A, B, C… +// - All feed hydration +message GetPostReplyCountsRequest { + repeated RecordRef refs = 1; +} + +message GetPostReplyCountsResponse { + repeated int32 counts = 1; +} + +// +// Labels +// + +// - Get all labels on a subjects A, B, C (uri or did) issued by dids D, E, F… +// - label hydration on nearly every view +message GetLabelsRequest { + repeated string subjects = 1; + repeated string issuers = 2; +} + +message GetLabelsResponse { + repeated bytes labels = 1; +} + +// +// Sync +// + +// - Latest repo rev of user w/ DID +// - Read-after-write header in`getProfile`, `getProfiles`, `getActorLikes`, `getAuthorFeed`, `getListFeed`, `getPostThread`, `getTimeline`. Could it be view dependent? +message GetLatestRevRequest { + string actor_did = 1; +} + +message GetLatestRevResponse { + string rev = 1; +} + + +message GetIdentityByDidRequest { + string did = 1; +} +message GetIdentityByDidResponse { + string did = 1; + string handle = 2; + bytes keys = 3; + bytes services = 4; + google.protobuf.Timestamp updated = 5; +} + +message GetIdentityByHandleRequest { + string handle = 1; +} +message GetIdentityByHandleResponse { + string handle = 1; + string did = 2; + bytes keys = 3; + bytes services = 4; + google.protobuf.Timestamp updated = 5; +} + + + +// +// Moderation +// + +message GetBlobTakedownRequest { + string did = 1; + string cid = 2; +} + +message GetBlobTakedownResponse { + bool taken_down = 1; + string takedown_ref = 2; +} + + + +message GetActorTakedownRequest { + string did = 1; +} + +message GetActorTakedownResponse { + bool taken_down = 1; + string takedown_ref = 2; +} + +message GetRecordTakedownRequest { + string record_uri = 1; +} + +message GetRecordTakedownResponse { + bool taken_down = 1; + string takedown_ref = 2; +} + + +// Ping +message PingRequest {} +message PingResponse {} + + + + +service Service { + // + // Read Path + // + + // Records + rpc GetBlockRecords(GetBlockRecordsRequest) returns (GetBlockRecordsResponse); + rpc GetFeedGeneratorRecords(GetFeedGeneratorRecordsRequest) returns (GetFeedGeneratorRecordsResponse); + rpc GetFollowRecords(GetFollowRecordsRequest) returns (GetFollowRecordsResponse); + rpc GetLikeRecords(GetLikeRecordsRequest) returns (GetLikeRecordsResponse); + rpc GetListBlockRecords(GetListBlockRecordsRequest) returns (GetListBlockRecordsResponse); + rpc GetListItemRecords(GetListItemRecordsRequest) returns (GetListItemRecordsResponse); + rpc GetListRecords(GetListRecordsRequest) returns (GetListRecordsResponse); + rpc GetPostRecords(GetPostRecordsRequest) returns (GetPostRecordsResponse); + rpc GetProfileRecords(GetProfileRecordsRequest) returns (GetProfileRecordsResponse); + rpc GetRepostRecords(GetRepostRecordsRequest) returns (GetRepostRecordsResponse); + rpc GetThreadGateRecords(GetThreadGateRecordsRequest) returns (GetThreadGateRecordsResponse); + + // Follows + rpc GetActorFollowsActors(GetActorFollowsActorsRequest) returns (GetActorFollowsActorsResponse); + rpc GetFollowers(GetFollowersRequest) returns (GetFollowersResponse); + rpc GetFollows(GetFollowsRequest) returns (GetFollowsResponse); + + // Likes + rpc GetLikesBySubject(GetLikesBySubjectRequest) returns (GetLikesBySubjectResponse); + rpc GetLikesByActorAndSubjects(GetLikesByActorAndSubjectsRequest) returns (GetLikesByActorAndSubjectsResponse); + rpc GetActorLikes(GetActorLikesRequest) returns (GetActorLikesResponse); + + // Reposts + rpc GetRepostsBySubject(GetRepostsBySubjectRequest) returns (GetRepostsBySubjectResponse); + rpc GetRepostsByActorAndSubjects(GetRepostsByActorAndSubjectsRequest) returns (GetRepostsByActorAndSubjectsResponse); + rpc GetActorReposts(GetActorRepostsRequest) returns (GetActorRepostsResponse); + + // Interaction Counts + rpc GetInteractionCounts(GetInteractionCountsRequest) returns (GetInteractionCountsResponse); + rpc GetCountsForUsers(GetCountsForUsersRequest) returns (GetCountsForUsersResponse); + + // Profile + rpc GetActors(GetActorsRequest) returns (GetActorsResponse); + rpc GetDidsByHandles(GetDidsByHandlesRequest) returns (GetDidsByHandlesResponse); + + // Relationships + rpc GetRelationships(GetRelationshipsRequest) returns (GetRelationshipsResponse); + rpc GetBlockExistence(GetBlockExistenceRequest) returns (GetBlockExistenceResponse); + + // Lists + rpc GetActorLists(GetActorListsRequest) returns (GetActorListsResponse); + rpc GetListMembers(GetListMembersRequest) returns (GetListMembersResponse); + rpc GetListMembership(GetListMembershipRequest) returns (GetListMembershipResponse); + rpc GetListCount(GetListCountRequest) returns (GetListCountResponse); + + // Mutes + rpc GetActorMutesActor(GetActorMutesActorRequest) returns (GetActorMutesActorResponse); + rpc GetMutes(GetMutesRequest) returns (GetMutesResponse); + + // Mutelists + rpc GetActorMutesActorViaList(GetActorMutesActorViaListRequest) returns (GetActorMutesActorViaListResponse); + rpc GetMutelistSubscription(GetMutelistSubscriptionRequest) returns (GetMutelistSubscriptionResponse); + rpc GetMutelistSubscriptions(GetMutelistSubscriptionsRequest) returns (GetMutelistSubscriptionsResponse); + + // Blocks + rpc GetBidirectionalBlock(GetBidirectionalBlockRequest) returns (GetBidirectionalBlockResponse); + rpc GetBlocks(GetBlocksRequest) returns (GetBlocksResponse); + + // Blocklists + rpc GetBidirectionalBlockViaList(GetBidirectionalBlockViaListRequest) returns (GetBidirectionalBlockViaListResponse); + rpc GetBlocklistSubscription(GetBlocklistSubscriptionRequest) returns (GetBlocklistSubscriptionResponse); + rpc GetBlocklistSubscriptions(GetBlocklistSubscriptionsRequest) returns (GetBlocklistSubscriptionsResponse); + + // Notifications + rpc GetNotifications(GetNotificationsRequest) returns (GetNotificationsResponse); + rpc GetNotificationSeen(GetNotificationSeenRequest) returns (GetNotificationSeenResponse); + rpc GetUnreadNotificationCount(GetUnreadNotificationCountRequest) returns (GetUnreadNotificationCountResponse); + rpc UpdateNotificationSeen(UpdateNotificationSeenRequest) returns (UpdateNotificationSeenResponse); + + // FeedGenerators + rpc GetActorFeeds(GetActorFeedsRequest) returns (GetActorFeedsResponse); + rpc GetSuggestedFeeds(GetSuggestedFeedsRequest) returns (GetSuggestedFeedsResponse); + rpc GetFeedGeneratorStatus(GetFeedGeneratorStatusRequest) returns (GetFeedGeneratorStatusResponse); + rpc SearchFeedGenerators(SearchFeedGeneratorsRequest) returns (SearchFeedGeneratorsResponse); + + // Feeds + rpc GetAuthorFeed(GetAuthorFeedRequest) returns (GetAuthorFeedResponse); + rpc GetTimeline(GetTimelineRequest) returns (GetTimelineResponse); + rpc GetListFeed(GetListFeedRequest) returns (GetListFeedResponse); + + // Threads + rpc GetThread(GetThreadRequest) returns (GetThreadResponse); + + // Search + rpc SearchActors(SearchActorsRequest) returns (SearchActorsResponse); + rpc SearchPosts(SearchPostsRequest) returns (SearchPostsResponse); + + // Suggestions + rpc GetFollowSuggestions(GetFollowSuggestionsRequest) returns (GetFollowSuggestionsResponse); + rpc GetSuggestedEntities(GetSuggestedEntitiesRequest) returns (GetSuggestedEntitiesResponse); + + // Posts + rpc GetPostReplyCounts(GetPostReplyCountsRequest) returns (GetPostReplyCountsResponse); + + // Labels + rpc GetLabels(GetLabelsRequest) returns (GetLabelsResponse); + + // Sync + rpc GetLatestRev(GetLatestRevRequest) returns (GetLatestRevResponse); + + // Moderation + rpc GetBlobTakedown(GetBlobTakedownRequest) returns (GetBlobTakedownResponse); + rpc GetRecordTakedown(GetRecordTakedownRequest) returns (GetRecordTakedownResponse); + rpc GetActorTakedown(GetActorTakedownRequest) returns (GetActorTakedownResponse); + + // Identity + rpc GetIdentityByDid(GetIdentityByDidRequest) returns (GetIdentityByDidResponse); + rpc GetIdentityByHandle(GetIdentityByHandleRequest) returns (GetIdentityByHandleResponse); + + // Ping + rpc Ping(PingRequest) returns (PingResponse); + + + + + // + // Write Path + // + + // Moderation + rpc TakedownBlob(TakedownBlobRequest) returns (TakedownBlobResponse); + rpc TakedownRecord(TakedownRecordRequest) returns (TakedownRecordResponse); + rpc TakedownActor(TakedownActorRequest) returns (TakedownActorResponse); + + rpc UntakedownBlob(UntakedownBlobRequest) returns (UntakedownBlobResponse); + rpc UntakedownRecord(UntakedownRecordRequest) returns (UntakedownRecordResponse); + rpc UntakedownActor(UntakedownActorRequest) returns (UntakedownActorResponse); + + // Ingestion + rpc CreateActorMute(CreateActorMuteRequest) returns (CreateActorMuteResponse); + rpc DeleteActorMute(DeleteActorMuteRequest) returns (DeleteActorMuteResponse); + rpc ClearActorMutes(ClearActorMutesRequest) returns (ClearActorMutesResponse); + + rpc CreateActorMutelistSubscription(CreateActorMutelistSubscriptionRequest) returns (CreateActorMutelistSubscriptionResponse); + rpc DeleteActorMutelistSubscription(DeleteActorMutelistSubscriptionRequest) returns (DeleteActorMutelistSubscriptionResponse); + rpc ClearActorMutelistSubscriptions(ClearActorMutelistSubscriptionsRequest) returns (ClearActorMutelistSubscriptionsResponse); +} + + +message TakedownActorRequest { + string did = 1; + string ref = 2; + google.protobuf.Timestamp seen = 3; +} + +message TakedownActorResponse { +} + +message UntakedownActorRequest { + string did = 1; + google.protobuf.Timestamp seen = 2; +} + +message UntakedownActorResponse { +} + +message TakedownBlobRequest { + string did = 1; + string cid = 2; + string ref = 3; + google.protobuf.Timestamp seen = 4; +} + +message TakedownBlobResponse {} + +message UntakedownBlobRequest { + string did = 1; + string cid = 2; + google.protobuf.Timestamp seen = 3; +} + +message UntakedownBlobResponse {} + +message TakedownRecordRequest { + string record_uri = 1; + string ref = 2; + google.protobuf.Timestamp seen = 3; +} + +message TakedownRecordResponse { +} + +message UntakedownRecordRequest { + string record_uri = 1; + google.protobuf.Timestamp seen = 2; +} + +message UntakedownRecordResponse { +} + +message CreateActorMuteRequest { + string actor_did = 1; + string subject_did = 2; +} + +message CreateActorMuteResponse {} + +message DeleteActorMuteRequest { + string actor_did = 1; + string subject_did = 2; +} + +message DeleteActorMuteResponse {} + +message ClearActorMutesRequest { + string actor_did = 1; +} + +message ClearActorMutesResponse {} + +message CreateActorMutelistSubscriptionRequest { + string actor_did = 1; + string subject_uri = 2; +} + +message CreateActorMutelistSubscriptionResponse {} + +message DeleteActorMutelistSubscriptionRequest { + string actor_did = 1; + string subject_uri = 2; +} + +message DeleteActorMutelistSubscriptionResponse {} + +message ClearActorMutelistSubscriptionsRequest { + string actor_did = 1; +} + +message ClearActorMutelistSubscriptionsResponse {} diff --git a/packages/bsky/src/api/app/bsky/actor/getProfile.ts b/packages/bsky/src/api/app/bsky/actor/getProfile.ts index 47c2f8f8ca7..386313aa5cb 100644 --- a/packages/bsky/src/api/app/bsky/actor/getProfile.ts +++ b/packages/bsky/src/api/app/bsky/actor/getProfile.ts @@ -1,108 +1,85 @@ import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import { QueryParams } from '../../../../lexicon/types/app/bsky/actor/getProfile' -import { softDeleted } from '../../../../db/util' import AppContext from '../../../../context' -import { Database } from '../../../../db' -import { Actor } from '../../../../db/tables/actor' -import { - ActorService, - ProfileDetailHydrationState, -} from '../../../../services/actor' import { setRepoRev } from '../../../util' import { createPipeline, noRules } from '../../../../pipeline' -import { ModerationService } from '../../../../services/moderation' +import { HydrationState, Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' export default function (server: Server, ctx: AppContext) { const getProfile = createPipeline(skeleton, hydration, noRules, presentation) server.app.bsky.actor.getProfile({ auth: ctx.authVerifier.optionalStandardOrRole, handler: async ({ auth, params, res }) => { - const db = ctx.db.getReplica() - const actorService = ctx.services.actor(db) - const modService = ctx.services.moderation(ctx.db.getPrimary()) const { viewer, canViewTakedowns } = ctx.authVerifier.parseCreds(auth) - const [result, repoRev] = await Promise.allSettled([ - getProfile( - { ...params, viewer, canViewTakedowns }, - { db, actorService, modService }, - ), - actorService.getRepoRev(viewer), - ]) + const result = await getProfile( + { ...params, viewer, canViewTakedowns }, + ctx, + ) - if (repoRev.status === 'fulfilled') { - setRepoRev(res, repoRev.value) - } - if (result.status === 'rejected') { - throw result.reason - } + const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) + setRepoRev(res, repoRev) return { encoding: 'application/json', - body: result.value, + body: result, } }, }) } -const skeleton = async ( - params: Params, - ctx: Context, -): Promise => { - const { actorService } = ctx - const { canViewTakedowns } = params - const actor = await actorService.getActor(params.actor, true) - if (!actor) { +const skeleton = async (input: { + ctx: Context + params: Params +}): Promise => { + const { ctx, params } = input + const [did] = await ctx.hydrator.actor.getDids([params.actor]) + if (!did) { throw new InvalidRequestError('Profile not found') } - if (!canViewTakedowns && softDeleted(actor)) { - if (actor.takedownRef?.includes('SUSPEND')) { - throw new InvalidRequestError( - 'Account has been temporarily suspended', - 'AccountTakedown', - ) - } else { - throw new InvalidRequestError( - 'Account has been taken down', - 'AccountTakedown', - ) - } - } - return { params, actor } + return { did } } -const hydration = async (state: SkeletonState, ctx: Context) => { - const { actorService } = ctx - const { params, actor } = state - const { viewer, canViewTakedowns } = params - const hydration = await actorService.views.profileDetailHydration( - [actor.did], - { viewer, includeSoftDeleted: canViewTakedowns }, +const hydration = async (input: { + ctx: Context + params: Params + skeleton: SkeletonState +}) => { + const { ctx, params, skeleton } = input + return ctx.hydrator.hydrateProfilesDetailed( + [skeleton.did], + params.viewer, + true, ) - return { ...state, ...hydration } } -const presentation = (state: HydrationState, ctx: Context) => { - const { actorService } = ctx - const { params, actor } = state - const { viewer } = params - const profiles = actorService.views.profileDetailPresentation( - [actor.did], - state, - { viewer }, - ) - const profile = profiles[actor.did] +const presentation = (input: { + ctx: Context + params: Params + skeleton: SkeletonState + hydration: HydrationState +}) => { + const { ctx, params, skeleton, hydration } = input + const profile = ctx.views.profileDetailed(skeleton.did, hydration) if (!profile) { throw new InvalidRequestError('Profile not found') + } else if ( + !params.canViewTakedowns && + ctx.views.actorIsTakendown(skeleton.did, hydration) + ) { + throw new InvalidRequestError( + 'Account has been suspended', + 'AccountTakedown', + ) } return profile } type Context = { - db: Database - actorService: ActorService - modService: ModerationService + hydrator: Hydrator + views: Views } type Params = QueryParams & { @@ -110,6 +87,4 @@ type Params = QueryParams & { canViewTakedowns: boolean } -type SkeletonState = { params: Params; actor: Actor } - -type HydrationState = SkeletonState & ProfileDetailHydrationState +type SkeletonState = { did: string } diff --git a/packages/bsky/src/api/app/bsky/actor/getProfiles.ts b/packages/bsky/src/api/app/bsky/actor/getProfiles.ts index 21ca13949d2..7a71340d892 100644 --- a/packages/bsky/src/api/app/bsky/actor/getProfiles.ts +++ b/packages/bsky/src/api/app/bsky/actor/getProfiles.ts @@ -2,28 +2,21 @@ import { mapDefined } from '@atproto/common' import { Server } from '../../../../lexicon' import { QueryParams } from '../../../../lexicon/types/app/bsky/actor/getProfiles' import AppContext from '../../../../context' -import { Database } from '../../../../db' -import { - ActorService, - ProfileDetailHydrationState, -} from '../../../../services/actor' import { setRepoRev } from '../../../util' import { createPipeline, noRules } from '../../../../pipeline' +import { HydrationState, Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' export default function (server: Server, ctx: AppContext) { const getProfile = createPipeline(skeleton, hydration, noRules, presentation) server.app.bsky.actor.getProfiles({ auth: ctx.authVerifier.standardOptional, handler: async ({ auth, params, res }) => { - const db = ctx.db.getReplica() - const actorService = ctx.services.actor(db) const viewer = auth.credentials.iss - const [result, repoRev] = await Promise.all([ - getProfile({ ...params, viewer }, { db, actorService }), - actorService.getRepoRev(viewer), - ]) + const result = await getProfile({ ...params, viewer }, ctx) + const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) setRepoRev(res, repoRev) return { @@ -34,45 +27,44 @@ export default function (server: Server, ctx: AppContext) { }) } -const skeleton = async ( - params: Params, - ctx: Context, -): Promise => { - const { actorService } = ctx - const actors = await actorService.getActors(params.actors) - return { params, dids: actors.map((a) => a.did) } +const skeleton = async (input: { + ctx: Context + params: Params +}): Promise => { + const { ctx, params } = input + const dids = await ctx.hydrator.actor.getDidsDefined(params.actors) + return { dids } } -const hydration = async (state: SkeletonState, ctx: Context) => { - const { actorService } = ctx - const { params, dids } = state - const { viewer } = params - const hydration = await actorService.views.profileDetailHydration(dids, { - viewer, - }) - return { ...state, ...hydration } +const hydration = async (input: { + ctx: Context + params: Params + skeleton: SkeletonState +}) => { + const { ctx, params, skeleton } = input + return ctx.hydrator.hydrateProfilesDetailed(skeleton.dids, params.viewer) } -const presentation = (state: HydrationState, ctx: Context) => { - const { actorService } = ctx - const { params, dids } = state - const { viewer } = params - const profiles = actorService.views.profileDetailPresentation(dids, state, { - viewer, - }) - const profileViews = mapDefined(dids, (did) => profiles[did]) - return { profiles: profileViews } +const presentation = (input: { + ctx: Context + params: Params + skeleton: SkeletonState + hydration: HydrationState +}) => { + const { ctx, skeleton, hydration } = input + const profiles = mapDefined(skeleton.dids, (did) => + ctx.views.profileDetailed(did, hydration), + ) + return { profiles } } type Context = { - db: Database - actorService: ActorService + hydrator: Hydrator + views: Views } type Params = QueryParams & { viewer: string | null } -type SkeletonState = { params: Params; dids: string[] } - -type HydrationState = SkeletonState & ProfileDetailHydrationState +type SkeletonState = { dids: string[] } diff --git a/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts b/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts index 3bfeccfa28e..622fcd3891c 100644 --- a/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts +++ b/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts @@ -1,13 +1,12 @@ import { mapDefined } from '@atproto/common' import AppContext from '../../../../context' -import { Database } from '../../../../db' -import { Actor } from '../../../../db/tables/actor' -import { notSoftDeletedClause } from '../../../../db/util' import { Server } from '../../../../lexicon' import { QueryParams } from '../../../../lexicon/types/app/bsky/actor/getSuggestions' import { createPipeline } from '../../../../pipeline' -import { ActorInfoMap, ActorService } from '../../../../services/actor' -import { BlockAndMuteState, GraphService } from '../../../../services/graph' +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 getSuggestions = createPipeline( @@ -19,15 +18,8 @@ export default function (server: Server, ctx: AppContext) { server.app.bsky.actor.getSuggestions({ auth: ctx.authVerifier.standardOptional, handler: async ({ params, auth }) => { - const db = ctx.db.getReplica() - const actorService = ctx.services.actor(db) - const graphService = ctx.services.graph(db) const viewer = auth.credentials.iss - - const result = await getSuggestions( - { ...params, viewer }, - { db, actorService, graphService }, - ) + const result = await getSuggestions({ ...params, viewer }, ctx) return { encoding: 'application/json', @@ -37,114 +29,80 @@ export default function (server: Server, ctx: AppContext) { }) } -const skeleton = async ( - params: Params, - ctx: Context, -): Promise => { - const { db } = ctx - const { viewer } = params - const alreadyIncluded = parseCursor(params.cursor) // @NOTE handles bad cursor e.g. on appview swap - const { ref } = db.db.dynamic - const suggestions = await db.db - .selectFrom('suggested_follow') - .innerJoin('actor', 'actor.did', 'suggested_follow.did') - .where(notSoftDeletedClause(ref('actor'))) - .where('suggested_follow.did', '!=', viewer ?? '') - .whereNotExists((qb) => - qb - .selectFrom('follow') - .selectAll() - .where('creator', '=', viewer ?? '') - .whereRef('subjectDid', '=', ref('actor.did')), - ) - .if(alreadyIncluded.length > 0, (qb) => - qb.where('suggested_follow.order', 'not in', alreadyIncluded), - ) - .selectAll() - .orderBy('suggested_follow.order', 'asc') - .execute() - - // always include first two - const firstTwo = suggestions.filter( - (row) => row.order === 1 || row.order === 2, - ) - const rest = suggestions.filter((row) => row.order !== 1 && row.order !== 2) - const limited = firstTwo.concat(shuffle(rest)).slice(0, params.limit) - - // if the result set ends up getting larger, consider using a seed included in the cursor for for the randomized shuffle - const cursor = - limited.length > 0 - ? limited - .map((row) => row.order.toString()) - .concat(alreadyIncluded.map((id) => id.toString())) - .join(':') - : undefined - - return { params, suggestions: limited, cursor } -} - -const hydration = async (state: SkeletonState, ctx: Context) => { - const { graphService, actorService } = ctx - const { params, suggestions } = state - const { viewer } = params - const [actors, bam] = await Promise.all([ - actorService.views.profiles(suggestions, viewer), - graphService.getBlockAndMuteState( - viewer ? suggestions.map((sug) => [viewer, sug.did]) : [], - ), - ]) - return { ...state, bam, actors } +const skeleton = async (input: { + ctx: Context + params: Params +}): Promise => { + const { ctx, params } = input + // @NOTE for appview swap moving to rkey-based cursors which are somewhat permissive, should not hard-break pagination + const suggestions = await ctx.dataplane.getFollowSuggestions({ + actorDid: params.viewer ?? undefined, + cursor: params.cursor, + limit: params.limit, + }) + let dids = suggestions.dids + if (params.viewer !== null) { + const follows = await ctx.dataplane.getActorFollowsActors({ + actorDid: params.viewer, + targetDids: dids, + }) + dids = dids.filter((did, i) => !follows.uris[i] && did !== params.viewer) + } + return { dids, cursor: parseString(suggestions.cursor) } } -const noBlocksOrMutes = (state: HydrationState) => { - const { viewer } = state.params - if (!viewer) return state - state.suggestions = state.suggestions.filter( - (item) => - !state.bam.block([viewer, item.did]) && - !state.bam.mute([viewer, item.did]), +const hydration = async (input: { + ctx: Context + params: Params + skeleton: Skeleton +}) => { + const { ctx, params, skeleton } = input + return ctx.hydrator.hydrateProfilesDetailed( + skeleton.dids, + params.viewer, + true, ) - return state } -const presentation = (state: HydrationState) => { - const { suggestions, actors, cursor } = state - const suggestedActors = mapDefined(suggestions, (sug) => actors[sug.did]) - return { actors: suggestedActors, cursor } +const noBlocksOrMutes = (input: { + ctx: Context + params: Params + skeleton: Skeleton + hydration: HydrationState +}) => { + const { ctx, skeleton, hydration } = input + skeleton.dids = skeleton.dids.filter( + (did) => + !ctx.views.viewerBlockExists(did, hydration) && + !ctx.views.viewerMuteExists(did, hydration), + ) + return skeleton } -const parseCursor = (cursor?: string): number[] => { - if (!cursor) { - return [] - } - try { - return cursor - .split(':') - .map((id) => parseInt(id, 10)) - .filter((id) => !isNaN(id)) - } catch { - return [] +const presentation = (input: { + ctx: Context + params: Params + skeleton: Skeleton + hydration: HydrationState +}) => { + const { ctx, skeleton, hydration } = input + const actors = mapDefined(skeleton.dids, (did) => + ctx.views.profile(did, hydration), + ) + return { + actors, + cursor: skeleton.cursor, } } -const shuffle = (arr: T[]): T[] => { - return arr - .map((value) => ({ value, sort: Math.random() })) - .sort((a, b) => a.sort - b.sort) - .map(({ value }) => value) -} - type Context = { - db: Database - actorService: ActorService - graphService: GraphService + dataplane: DataPlaneClient + hydrator: Hydrator + views: Views } -type Params = QueryParams & { viewer: string | null } - -type SkeletonState = { params: Params; suggestions: Actor[]; cursor?: string } - -type HydrationState = SkeletonState & { - bam: BlockAndMuteState - actors: ActorInfoMap +type Params = QueryParams & { + viewer: string | null } + +type Skeleton = { dids: string[]; cursor?: string } diff --git a/packages/bsky/src/api/app/bsky/actor/searchActors.ts b/packages/bsky/src/api/app/bsky/actor/searchActors.ts index 82f0327ef89..403be45892a 100644 --- a/packages/bsky/src/api/app/bsky/actor/searchActors.ts +++ b/packages/bsky/src/api/app/bsky/actor/searchActors.ts @@ -1,56 +1,111 @@ import AppContext from '../../../../context' import { Server } from '../../../../lexicon' -import { cleanQuery } from '../../../../services/util/search' +import { mapDefined } from '@atproto/common' +import AtpAgent from '@atproto/api' +import { QueryParams } from '../../../../lexicon/types/app/bsky/actor/searchActors' +import { + HydrationFnInput, + PresentationFnInput, + RulesFnInput, + SkeletonFnInput, + createPipeline, +} from '../../../../pipeline' +import { 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 searchActors = createPipeline( + skeleton, + hydration, + noBlocks, + presentation, + ) server.app.bsky.actor.searchActors({ auth: ctx.authVerifier.standardOptional, handler: async ({ auth, params }) => { - const { cursor, limit } = params - const requester = auth.credentials.iss - const rawQuery = params.q ?? params.term - const query = cleanQuery(rawQuery || '') - const db = ctx.db.getReplica('search') - - let results: string[] - let resCursor: string | undefined - if (ctx.searchAgent) { - // @NOTE cursors wont change on appview swap - 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 = await ctx.services - .actor(ctx.db.getReplica('search')) - .getSearchResults({ query, limit, cursor }) - results = res.results.map((a) => a.did) - resCursor = res.cursor + const viewer = auth.credentials.iss + const results = await searchActors({ ...params, viewer }, ctx) + return { + encoding: 'application/json', + body: results, } + }, + }) +} + +const skeleton = async (inputs: SkeletonFnInput) => { + const { ctx, params } = inputs + const term = params.q ?? params.term ?? '' - const actors = await ctx.services - .actor(db) - .views.profiles(results, requester) + // @TODO + // add hits total - const SKIP = [] - const filtered = results.flatMap((did) => { - const actor = actors[did] - if (!actor) return SKIP - if (actor.viewer?.blocking || actor.viewer?.blockedBy) return SKIP - return actor + if (ctx.searchAgent) { + // @NOTE cursors wont change on appview swap + const { data: res } = + await ctx.searchAgent.api.app.bsky.unspecced.searchActorsSkeleton({ + q: term, + cursor: params.cursor, + limit: params.limit, }) + return { + dids: res.actors.map(({ did }) => did), + cursor: parseString(res.cursor), + } + } - return { - encoding: 'application/json', - body: { - cursor: resCursor, - actors: filtered, - }, - } - }, + const res = await ctx.dataplane.searchActors({ + term, + limit: params.limit, + cursor: params.cursor, }) + return { + dids: res.dids, + cursor: parseString(res.cursor), + } +} + +const hydration = async ( + inputs: HydrationFnInput, +) => { + const { ctx, params, skeleton } = inputs + return ctx.hydrator.hydrateProfiles(skeleton.dids, params.viewer) +} + +const noBlocks = (inputs: RulesFnInput) => { + const { ctx, skeleton, hydration } = inputs + skeleton.dids = skeleton.dids.filter( + (did) => !ctx.views.viewerBlockExists(did, hydration), + ) + return skeleton +} + +const presentation = ( + inputs: PresentationFnInput, +) => { + const { ctx, skeleton, hydration } = inputs + const actors = mapDefined(skeleton.dids, (did) => + ctx.views.profile(did, hydration), + ) + return { + actors, + cursor: skeleton.cursor, + } +} + +type Context = { + dataplane: DataPlaneClient + hydrator: Hydrator + views: Views + searchAgent?: AtpAgent +} + +type Params = QueryParams & { viewer: string | null } + +type Skeleton = { + dids: string[] + hitsTotal?: number + cursor?: string } diff --git a/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts b/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts index 6a3167fd2d0..8c7507961a7 100644 --- a/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts +++ b/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts @@ -1,56 +1,107 @@ import AppContext from '../../../../context' import { Server } from '../../../../lexicon' +import AtpAgent from '@atproto/api' +import { mapDefined } from '@atproto/common' +import { QueryParams } from '../../../../lexicon/types/app/bsky/actor/searchActorsTypeahead' import { - cleanQuery, - getUserSearchQuerySimple, -} from '../../../../services/util/search' + HydrationFnInput, + PresentationFnInput, + RulesFnInput, + SkeletonFnInput, + createPipeline, +} from '../../../../pipeline' +import { 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 searchActorsTypeahead = createPipeline( + skeleton, + hydration, + noBlocks, + presentation, + ) server.app.bsky.actor.searchActorsTypeahead({ auth: ctx.authVerifier.standardOptional, handler: async ({ params, auth }) => { - const { limit } = params - const requester = auth.credentials.iss - const rawQuery = params.q ?? params.term - const query = cleanQuery(rawQuery || '') - const db = ctx.db.getReplica('search') - - 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 viewer = auth.credentials.iss + const results = await searchActorsTypeahead({ ...params, viewer }, ctx) + return { + encoding: 'application/json', + body: results, } + }, + }) +} + +const skeleton = async (inputs: SkeletonFnInput) => { + const { ctx, params } = inputs + const term = params.q ?? params.term ?? '' - const actors = await ctx.services - .actor(db) - .views.profilesBasic(results, requester) + // @TODO + // add typeahead option + // add hits total - const SKIP = [] - const filtered = results.flatMap((did) => { - const actor = actors[did] - if (!actor) return SKIP - if (actor.viewer?.blocking || actor.viewer?.blockedBy) return SKIP - return actor + if (ctx.searchAgent) { + const { data: res } = + await ctx.searchAgent.api.app.bsky.unspecced.searchActorsSkeleton({ + typeahead: true, + q: term, + limit: params.limit, }) + return { + dids: res.actors.map(({ did }) => did), + cursor: parseString(res.cursor), + } + } - return { - encoding: 'application/json', - body: { - actors: filtered, - }, - } - }, + const res = await ctx.dataplane.searchActors({ + term, + limit: params.limit, }) + return { + dids: res.dids, + cursor: parseString(res.cursor), + } +} + +const hydration = async ( + inputs: HydrationFnInput, +) => { + const { ctx, params, skeleton } = inputs + return ctx.hydrator.hydrateProfilesBasic(skeleton.dids, params.viewer) +} + +const noBlocks = (inputs: RulesFnInput) => { + const { ctx, skeleton, hydration } = inputs + skeleton.dids = skeleton.dids.filter( + (did) => !ctx.views.viewerBlockExists(did, hydration), + ) + return skeleton +} + +const presentation = ( + inputs: PresentationFnInput, +) => { + const { ctx, skeleton, hydration } = inputs + const actors = mapDefined(skeleton.dids, (did) => + ctx.views.profileBasic(did, hydration), + ) + return { + actors, + } +} + +type Context = { + dataplane: DataPlaneClient + hydrator: Hydrator + views: Views + searchAgent?: AtpAgent +} + +type Params = QueryParams & { viewer: string | null } + +type Skeleton = { + dids: string[] } diff --git a/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts b/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts index 266839c7711..b138ae1acad 100644 --- a/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts +++ b/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts @@ -1,69 +1,91 @@ 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 { createPipeline, noRules } from '../../../../pipeline' +import { HydrationState, Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { DataPlaneClient } from '../../../../data-plane' +import { parseString } from '../../../../hydration/util' +import { clearlyBadCursor } from '../../../util' export default function (server: Server, ctx: AppContext) { + const getActorFeeds = createPipeline( + skeleton, + hydration, + noRules, + presentation, + ) server.app.bsky.feed.getActorFeeds({ auth: ctx.authVerifier.standardOptional, handler: async ({ auth, params }) => { - const { actor, limit, cursor } = params const viewer = auth.credentials.iss - if (TimeCidKeyset.clearlyBad(cursor)) { - return { - encoding: 'application/json', - body: { feeds: [] }, - } + const result = await getActorFeeds({ ...params, viewer }, ctx) + return { + encoding: 'application/json', + body: result, } + }, + }) +} - 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 skeleton = async (inputs: { + ctx: Context + params: Params +}): Promise => { + const { ctx, params } = inputs + if (clearlyBadCursor(params.cursor)) { + return { feedUris: [] } + } + 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 { ref } = db.db.dynamic - let feedsQb = feedService - .selectFeedGeneratorQb(viewer) - .where('feed_generator.creator', '=', creatorRes.did) +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 keyset = new TimeCidKeyset( - ref('feed_generator.createdAt'), - ref('feed_generator.cid'), - ) - feedsQb = paginate(feedsQb, { - limit, - cursor, - keyset, - }) +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 [feedsRes, profiles] = await Promise.all([ - feedsQb.execute(), - actorService.views.profiles([creatorRes], viewer), - ]) - if (!profiles[creatorRes.did]) { - throw new InvalidRequestError(`Actor not found: ${actor}`) - } +type Context = { + hydrator: Hydrator + views: Views + dataplane: DataPlaneClient +} - const feeds = mapDefined(feedsRes, (row) => { - const feed = { - ...row, - viewer: viewer ? { like: row.viewerLike } : undefined, - } - return feedService.views.formatFeedGeneratorView(feed, profiles) - }) +type Params = QueryParams & { viewer: string | null } - return { - encoding: 'application/json', - body: { - cursor: keyset.packFromResult(feedsRes), - feeds, - }, - } - }, - }) +type Skeleton = { + feedUris: string[] + cursor?: string } diff --git a/packages/bsky/src/api/app/bsky/feed/getActorLikes.ts b/packages/bsky/src/api/app/bsky/feed/getActorLikes.ts index 48d6437e494..4f026418bc4 100644 --- a/packages/bsky/src/api/app/bsky/feed/getActorLikes.ts +++ b/packages/bsky/src/api/app/bsky/feed/getActorLikes.ts @@ -1,19 +1,16 @@ import { InvalidRequestError } from '@atproto/xrpc-server' +import { mapDefined } from '@atproto/common' import { Server } from '../../../../lexicon' import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getActorLikes' -import { FeedKeyset } from '../util/feed' -import { paginate } from '../../../../db/pagination' import AppContext from '../../../../context' -import { setRepoRev } from '../../../util' -import { - FeedHydrationState, - FeedRow, - FeedService, -} from '../../../../services/feed' -import { Database } from '../../../../db' -import { ActorService } from '../../../../services/actor' -import { GraphService } from '../../../../services/graph' +import { clearlyBadCursor, setRepoRev } from '../../../util' import { createPipeline } from '../../../../pipeline' +import { HydrationState, Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { DataPlaneClient } from '../../../../data-plane' +import { parseString } from '../../../../hydration/util' +import { creatorFromUri } from '../../../../views/util' +import { FeedItem } from '../../../../hydration/feed' export default function (server: Server, ctx: AppContext) { const getActorLikes = createPipeline( @@ -26,19 +23,10 @@ export default function (server: Server, ctx: AppContext) { auth: ctx.authVerifier.standardOptional, handler: async ({ params, auth, res }) => { const viewer = auth.credentials.iss - const db = ctx.db.getReplica() - const actorService = ctx.services.actor(db) - const feedService = ctx.services.feed(db) - const graphService = ctx.services.graph(db) - const [result, repoRev] = await Promise.all([ - getActorLikes( - { ...params, viewer }, - { db, actorService, feedService, graphService }, - ), - actorService.getRepoRev(viewer), - ]) + const result = await getActorLikes({ ...params, viewer }, ctx) + const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) setRepoRev(res, repoRev) return { @@ -49,81 +37,80 @@ export default function (server: Server, ctx: AppContext) { }) } -const skeleton = async ( - params: Params, - ctx: Context, -): Promise => { - const { db, actorService, feedService } = ctx +const skeleton = async (inputs: { + ctx: Context + params: Params +}): Promise => { + const { ctx, params } = inputs const { actor, limit, cursor, viewer } = params - const { ref } = db.db.dynamic - - const actorRes = await actorService.getActor(actor) - if (!actorRes) { - throw new InvalidRequestError('Profile not found') + if (clearlyBadCursor(cursor)) { + return { items: [] } } - const actorDid = actorRes.did - - if (!viewer || viewer !== actorDid) { + const [actorDid] = await ctx.hydrator.actor.getDids([actor]) + if (!actorDid || !viewer || viewer !== actorDid) { throw new InvalidRequestError('Profile not found') } - if (FeedKeyset.clearlyBad(cursor)) { - return { params, feedItems: [] } - } - - let feedItemsQb = feedService - .selectFeedItemQb() - .innerJoin('like', 'like.subject', 'feed_item.uri') - .where('like.creator', '=', actorDid) - - const keyset = new FeedKeyset(ref('like.sortAt'), ref('like.cid')) - - feedItemsQb = paginate(feedItemsQb, { + const likesRes = await ctx.dataplane.getActorLikes({ + actorDid, limit, cursor, - keyset, }) - const feedItems = await feedItemsQb.execute() + const items = likesRes.likes.map((l) => ({ post: { uri: l.subject } })) - return { params, feedItems, cursor: keyset.packFromResult(feedItems) } + return { + items, + cursor: parseString(likesRes.cursor), + } } -const hydration = async (state: SkeletonState, ctx: Context) => { - const { feedService } = ctx - const { params, feedItems } = state - const refs = feedService.feedItemRefs(feedItems) - const hydrated = await feedService.feedHydration({ - ...refs, - viewer: params.viewer, - }) - return { ...state, ...hydrated } +const hydration = async (inputs: { + ctx: Context + params: Params + skeleton: Skeleton +}) => { + const { ctx, params, skeleton } = inputs + return await ctx.hydrator.hydrateFeedItems(skeleton.items, params.viewer) } -const noPostBlocks = (state: HydrationState) => { - const { viewer } = state.params - state.feedItems = state.feedItems.filter( - (item) => !viewer || !state.bam.block([viewer, item.postAuthorDid]), - ) - return state +const noPostBlocks = (inputs: { + ctx: Context + skeleton: Skeleton + hydration: HydrationState +}) => { + const { ctx, skeleton, hydration } = inputs + skeleton.items = skeleton.items.filter((item) => { + const creator = creatorFromUri(item.post.uri) + return !ctx.views.viewerBlockExists(creator, hydration) + }) + return skeleton } -const presentation = (state: HydrationState, ctx: Context) => { - const { feedService } = ctx - const { feedItems, cursor, params } = state - const feed = feedService.views.formatFeed(feedItems, state, params.viewer) - return { feed, cursor } +const presentation = (inputs: { + ctx: Context + skeleton: Skeleton + hydration: HydrationState +}) => { + const { ctx, skeleton, hydration } = inputs + const feed = mapDefined(skeleton.items, (item) => + ctx.views.feedViewPost(item, hydration), + ) + return { + feed, + cursor: skeleton.cursor, + } } type Context = { - db: Database - feedService: FeedService - actorService: ActorService - graphService: GraphService + hydrator: Hydrator + views: Views + dataplane: DataPlaneClient } type Params = QueryParams & { viewer: string | null } -type SkeletonState = { params: Params; feedItems: FeedRow[]; cursor?: string } - -type HydrationState = SkeletonState & FeedHydrationState +type Skeleton = { + items: FeedItem[] + cursor?: string +} diff --git a/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts b/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts index 6b344547a45..02e2240828f 100644 --- a/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts +++ b/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts @@ -1,19 +1,21 @@ +import { mapDefined } from '@atproto/common' import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getAuthorFeed' -import { FeedKeyset } from '../util/feed' -import { paginate } from '../../../../db/pagination' import AppContext from '../../../../context' -import { setRepoRev } from '../../../util' -import { Database } from '../../../../db' -import { - FeedHydrationState, - FeedRow, - FeedService, -} from '../../../../services/feed' -import { ActorService } from '../../../../services/actor' -import { GraphService } from '../../../../services/graph' +import { clearlyBadCursor, setRepoRev } from '../../../util' import { createPipeline } from '../../../../pipeline' +import { + HydrationState, + Hydrator, + mergeStates, +} from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { DataPlaneClient } from '../../../../data-plane' +import { parseString } from '../../../../hydration/util' +import { Actor } from '../../../../hydration/actor' +import { FeedItem } from '../../../../hydration/feed' +import { FeedType } from '../../../../proto/bsky_pb' export default function (server: Server, ctx: AppContext) { const getAuthorFeed = createPipeline( @@ -25,24 +27,14 @@ export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getAuthorFeed({ auth: ctx.authVerifier.optionalStandardOrRole, handler: async ({ params, auth, res }) => { - const db = ctx.db.getReplica() - const actorService = ctx.services.actor(db) - const feedService = ctx.services.feed(db) - const graphService = ctx.services.graph(db) - const { viewer } = ctx.authVerifier.parseCreds(auth) + const { viewer, canViewTakedowns } = ctx.authVerifier.parseCreds(auth) - const [result, repoRev] = await Promise.all([ - getAuthorFeed( - { - ...params, - includeSoftDeleted: auth.credentials.type === 'role', - viewer, - }, - { db, actorService, feedService, graphService }, - ), - actorService.getRepoRev(viewer), - ]) + const result = await getAuthorFeed( + { ...params, viewer, includeTakedowns: canViewTakedowns }, + ctx, + ) + const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) setRepoRev(res, repoRev) return { @@ -53,135 +45,122 @@ export default function (server: Server, ctx: AppContext) { }) } -export const skeleton = async ( - params: Params, - ctx: Context, -): Promise => { - const { cursor, limit, actor, filter, viewer, includeSoftDeleted } = params - const { db, actorService, feedService, graphService } = ctx - const { ref } = db.db.dynamic +const FILTER_TO_FEED_TYPE = { + posts_with_replies: undefined, // default: all posts, replies, and reposts + posts_no_replies: FeedType.POSTS_NO_REPLIES, + posts_with_media: FeedType.POSTS_WITH_MEDIA, + posts_and_author_threads: FeedType.POSTS_AND_AUTHOR_THREADS, +} - // maybe resolve did first - const actorRes = await actorService.getActor(actor, includeSoftDeleted) - if (!actorRes) { +export const skeleton = async (inputs: { + ctx: Context + params: Params +}): Promise => { + const { ctx, params } = inputs + const [did] = await ctx.hydrator.actor.getDids([params.actor]) + if (!did) { throw new InvalidRequestError('Profile not found') } - const actorDid = actorRes.did - - // verify there is not a block between requester & subject - if (viewer !== null) { - const blocks = await graphService.getBlockState([[viewer, actorDid]]) - if (blocks.blocking([viewer, actorDid])) { - throw new InvalidRequestError( - `Requester has blocked actor: ${actor}`, - 'BlockedActor', - ) - } - if (blocks.blockedBy([viewer, actorDid])) { - throw new InvalidRequestError( - `Requester is blocked by actor: $${actor}`, - 'BlockedByActor', - ) - } + const actors = await ctx.hydrator.actor.getActors( + [did], + params.includeTakedowns, + ) + const actor = actors.get(did) + if (!actor) { + throw new InvalidRequestError('Profile not found') } - - if (FeedKeyset.clearlyBad(cursor)) { - return { params, feedItems: [] } + if (clearlyBadCursor(params.cursor)) { + return { actor, items: [] } } - - // defaults to posts, reposts, and replies - let feedItemsQb = feedService - .selectFeedItemQb() - .where('originatorDid', '=', actorDid) - - if (filter === 'posts_with_media') { - feedItemsQb = feedItemsQb - // only your own posts - .where('type', '=', 'post') - // only posts with media - .whereExists((qb) => - qb - .selectFrom('post_embed_image') - .select('post_embed_image.postUri') - .whereRef('post_embed_image.postUri', '=', 'feed_item.postUri'), - ) - } else if (filter === 'posts_no_replies') { - feedItemsQb = feedItemsQb.where((qb) => - qb.where('post.replyParent', 'is', null).orWhere('type', '=', 'repost'), - ) - } else if (filter === 'posts_and_author_threads') { - feedItemsQb = feedItemsQb.where((qb) => - qb - .where('type', '=', 'repost') - .orWhere('post.replyParent', 'is', null) - .orWhere('post.replyRoot', 'like', `at://${actorDid}/%`), - ) - } - - const keyset = new FeedKeyset(ref('feed_item.sortAt'), ref('feed_item.cid')) - - feedItemsQb = paginate(feedItemsQb, { - limit, - cursor, - keyset, + const res = await ctx.dataplane.getAuthorFeed({ + actorDid: did, + limit: params.limit, + cursor: params.cursor, + feedType: FILTER_TO_FEED_TYPE[params.filter], }) - - const feedItems = await feedItemsQb.execute() - return { - params, - feedItems, - cursor: keyset.packFromResult(feedItems), + actor, + items: res.items.map((item) => ({ + post: { uri: item.uri, cid: item.cid || undefined }, + repost: item.repost + ? { uri: item.repost, cid: item.repostCid || undefined } + : undefined, + })), + cursor: parseString(res.cursor), } } -const hydration = async (state: SkeletonState, ctx: Context) => { - const { feedService } = ctx - const { params, feedItems } = state - const refs = feedService.feedItemRefs(feedItems) - const hydrated = await feedService.feedHydration({ - ...refs, - viewer: params.viewer, - includeSoftDeleted: params.includeSoftDeleted, - }) - return { ...state, ...hydrated } +const hydration = async (inputs: { + ctx: Context + params: Params + skeleton: Skeleton +}): Promise => { + const { ctx, params, skeleton } = inputs + const [feedPostState, profileViewerState = {}] = await Promise.all([ + ctx.hydrator.hydrateFeedItems( + skeleton.items, + params.viewer, + params.includeTakedowns, + ), + params.viewer + ? ctx.hydrator.hydrateProfileViewers([skeleton.actor.did], params.viewer) + : undefined, + ]) + return mergeStates(feedPostState, profileViewerState) } -const noBlocksOrMutedReposts = (state: HydrationState) => { - const { viewer } = state.params - state.feedItems = state.feedItems.filter((item) => { - if (!viewer) return true +const noBlocksOrMutedReposts = (inputs: { + ctx: Context + skeleton: Skeleton + hydration: HydrationState +}): Skeleton => { + const { ctx, skeleton, hydration } = inputs + const relationship = hydration.profileViewers?.get(skeleton.actor.did) + if (relationship?.blocking || relationship?.blockingByList) { + throw new InvalidRequestError( + `Requester has blocked actor: ${skeleton.actor.did}`, + 'BlockedActor', + ) + } + if (relationship?.blockedBy || relationship?.blockedByList) { + throw new InvalidRequestError( + `Requester is blocked by actor: ${skeleton.actor.did}`, + 'BlockedByActor', + ) + } + skeleton.items = skeleton.items.filter((item) => { + const bam = ctx.views.feedItemBlocksAndMutes(item, hydration) return ( - !state.bam.block([viewer, item.postAuthorDid]) && - (item.type === 'post' || !state.bam.mute([viewer, item.postAuthorDid])) + !bam.authorBlocked && + !bam.originatorBlocked && + !(bam.authorMuted && !bam.originatorMuted) ) }) - return state + return skeleton } -const presentation = (state: HydrationState, ctx: Context) => { - const { feedService } = ctx - const { feedItems, cursor, params } = state - const feed = feedService.views.formatFeed(feedItems, state, params.viewer) - return { feed, cursor } +const presentation = (inputs: { + ctx: Context + skeleton: Skeleton + hydration: HydrationState +}) => { + const { ctx, skeleton, hydration } = inputs + const feed = mapDefined(skeleton.items, (item) => + ctx.views.feedViewPost(item, hydration), + ) + return { feed, cursor: skeleton.cursor } } type Context = { - db: Database - actorService: ActorService - feedService: FeedService - graphService: GraphService + hydrator: Hydrator + views: Views + dataplane: DataPlaneClient } -type Params = QueryParams & { - viewer: string | null - includeSoftDeleted: boolean -} +type Params = QueryParams & { viewer: string | null; includeTakedowns: boolean } -type SkeletonState = { - params: Params - feedItems: FeedRow[] +type Skeleton = { + actor: Actor + items: FeedItem[] cursor?: string } - -type HydrationState = SkeletonState & FeedHydrationState diff --git a/packages/bsky/src/api/app/bsky/feed/getFeed.ts b/packages/bsky/src/api/app/bsky/feed/getFeed.ts index 66461cd3bbb..aae633e4f2a 100644 --- a/packages/bsky/src/api/app/bsky/feed/getFeed.ts +++ b/packages/bsky/src/api/app/bsky/feed/getFeed.ts @@ -1,3 +1,4 @@ +import { mapDefined } from '@atproto/common' import { InvalidRequestError, UpstreamFailureError, @@ -5,25 +6,27 @@ import { serverTimingHeader, } from '@atproto/xrpc-server' import { ResponseType, XRPCError } from '@atproto/xrpc' -import { - DidDocument, - PoorlyFormattedDidDocumentError, - getFeedGen, -} from '@atproto/identity' import { AtpAgent, AppBskyFeedGetFeedSkeleton } from '@atproto/api' import { noUndefinedVals } from '@atproto/common' import { QueryParams as GetFeedParams } from '../../../../lexicon/types/app/bsky/feed/getFeed' import { OutputSchema as SkeletonOutput } from '../../../../lexicon/types/app/bsky/feed/getFeedSkeleton' -import { SkeletonFeedPost } from '../../../../lexicon/types/app/bsky/feed/defs' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { Database } from '../../../../db' import { - FeedHydrationState, - FeedRow, - FeedService, -} from '../../../../services/feed' -import { createPipeline } from '../../../../pipeline' + HydrationFnInput, + PresentationFnInput, + RulesFnInput, + SkeletonFnInput, + createPipeline, +} from '../../../../pipeline' +import { FeedItem } from '../../../../hydration/feed' +import { GetIdentityByDidResponse } from '../../../../proto/bsky_pb' +import { + Code, + getServiceEndpoint, + isDataplaneError, + unpackIdentityServices, +} from '../../../../data-plane' export default function (server: Server, ctx: AppContext) { const getFeed = createPipeline( @@ -35,8 +38,6 @@ export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getFeed({ auth: ctx.authVerifier.standardOptionalAnyAud, handler: async ({ params, auth, req }) => { - const db = ctx.db.getReplica() - const feedService = ctx.services.feed(db) const viewer = auth.credentials.iss const headers = noUndefinedVals({ authorization: req.headers['authorization'], @@ -44,13 +45,8 @@ export default function (server: Server, ctx: AppContext) { }) // @NOTE feed cursors should not be affected by appview swap const { timerSkele, timerHydr, resHeaders, ...result } = await getFeed( - { ...params, viewer }, - { - db, - feedService, - appCtx: ctx, - headers, - }, + { ...params, viewer, headers }, + ctx, ) return { @@ -66,125 +62,113 @@ export default function (server: Server, ctx: AppContext) { } const skeleton = async ( - params: Params, - ctx: Context, -): Promise => { + inputs: SkeletonFnInput, +): Promise => { + const { ctx, params } = inputs const timerSkele = new ServerTimer('skele').start() - const feedParams: GetFeedParams = { - feed: params.feed, - limit: params.limit, - cursor: params.cursor, - } - const { feedItems, cursor, resHeaders, ...passthrough } = - await skeletonFromFeedGen(ctx, feedParams) + const { + feedItems: algoItems, + cursor, + resHeaders, + ...passthrough + } = await skeletonFromFeedGen(ctx, params) + return { - params, cursor, - feedItems, + items: algoItems.map(toFeedItem), timerSkele: timerSkele.stop(), + timerHydr: new ServerTimer('hydr').start(), resHeaders, passthrough, } } -const hydration = async (state: SkeletonState, ctx: Context) => { +const hydration = async ( + inputs: HydrationFnInput, +) => { + const { ctx, params, skeleton } = inputs const timerHydr = new ServerTimer('hydr').start() - const { feedService } = ctx - const { params, feedItems } = state - const refs = feedService.feedItemRefs(feedItems) - const hydrated = await feedService.feedHydration({ - ...refs, - viewer: params.viewer, - }) - return { ...state, ...hydrated, timerHydr: timerHydr.stop() } + const hydration = await ctx.hydrator.hydrateFeedItems( + skeleton.items, + params.viewer, + ) + skeleton.timerHydr = timerHydr.stop() + return hydration } -const noBlocksOrMutes = (state: HydrationState) => { - const { viewer } = state.params - state.feedItems = state.feedItems.filter((item) => { - if (!viewer) return true +const noBlocksOrMutes = (inputs: RulesFnInput) => { + const { ctx, skeleton, hydration } = inputs + skeleton.items = skeleton.items.filter((item) => { + const bam = ctx.views.feedItemBlocksAndMutes(item, hydration) return ( - !state.bam.block([viewer, item.postAuthorDid]) && - !state.bam.block([viewer, item.originatorDid]) && - !state.bam.mute([viewer, item.postAuthorDid]) && - !state.bam.mute([viewer, item.originatorDid]) + !bam.authorBlocked && + !bam.authorMuted && + !bam.originatorBlocked && + !bam.originatorMuted ) }) - return state + return skeleton } -const presentation = (state: HydrationState, ctx: Context) => { - const { feedService } = ctx - const { feedItems, cursor, passthrough, params } = state - const feed = feedService.views.formatFeed(feedItems, state, params.viewer) +const presentation = ( + inputs: PresentationFnInput, +) => { + const { ctx, params, skeleton, hydration } = inputs + const feed = mapDefined(skeleton.items, (item) => { + return ctx.views.feedViewPost(item, hydration) + }).slice(0, params.limit) return { feed, - cursor, - timerSkele: state.timerSkele, - timerHydr: state.timerHydr, - resHeaders: state.resHeaders, - ...passthrough, + cursor: skeleton.cursor, + timerSkele: skeleton.timerSkele, + timerHydr: skeleton.timerHydr, + resHeaders: skeleton.resHeaders, + ...skeleton.passthrough, } } -type Context = { - db: Database - feedService: FeedService - appCtx: AppContext +type Context = AppContext + +type Params = GetFeedParams & { + viewer: string | null headers: Record } -type Params = GetFeedParams & { viewer: string | null } - -type SkeletonState = { - params: Params - feedItems: FeedRow[] +type Skeleton = { + items: FeedItem[] passthrough: Record // pass through additional items in feedgen response resHeaders?: Record cursor?: string timerSkele: ServerTimer -} - -type HydrationState = SkeletonState & - FeedHydrationState & { feedItems: FeedRow[]; timerHydr: ServerTimer } - -type AlgoResponse = { - feedItems: FeedRow[] - resHeaders?: Record - cursor?: string + timerHydr: ServerTimer } const skeletonFromFeedGen = async ( ctx: Context, - params: GetFeedParams, + params: Params, ): Promise => { - const { db, appCtx, headers } = ctx - const { feed } = params - // Resolve and fetch feed skeleton - const found = await db.db - .selectFrom('feed_generator') - .where('uri', '=', feed) - .select('feedDid') - .executeTakeFirst() - if (!found) { + const { feed, headers } = params + const found = await ctx.hydrator.feed.getFeedGens([feed], true) + const feedDid = await found.get(feed)?.record.did + if (!feedDid) { throw new InvalidRequestError('could not find feed') } - const feedDid = found.feedDid - let resolved: DidDocument | null + let identity: GetIdentityByDidResponse try { - resolved = await appCtx.idResolver.did.resolve(feedDid) + identity = await ctx.dataplane.getIdentityByDid({ did: feedDid }) } catch (err) { - if (err instanceof PoorlyFormattedDidDocumentError) { - throw new InvalidRequestError(`invalid did document: ${feedDid}`) + if (isDataplaneError(err, Code.NotFound)) { + throw new InvalidRequestError(`could not resolve identity: ${feedDid}`) } throw err } - if (!resolved) { - throw new InvalidRequestError(`could not resolve did document: ${feedDid}`) - } - const fgEndpoint = getFeedGen(resolved) + const services = unpackIdentityServices(identity.services) + const fgEndpoint = getServiceEndpoint(services, { + id: 'bsky_fg', + type: 'BskyFeedGenerator', + }) if (!fgEndpoint) { throw new InvalidRequestError( `invalid feed generator service details in did document: ${feedDid}`, @@ -197,9 +181,16 @@ const skeletonFromFeedGen = async ( let resHeaders: Record | undefined = undefined try { // @TODO currently passthrough auth headers from pds - const result = await agent.api.app.bsky.feed.getFeedSkeleton(params, { - headers, - }) + const result = await agent.api.app.bsky.feed.getFeedSkeleton( + { + feed: params.feed, + limit: params.limit, + cursor: params.cursor, + }, + { + headers, + }, + ) skeleton = result.data if (result.headers['content-language']) { resHeaders = { @@ -225,33 +216,30 @@ const skeletonFromFeedGen = async ( } const { feed: feedSkele, ...skele } = skeleton - const feedItems = await skeletonToFeedItems( - feedSkele.slice(0, params.limit), - ctx, - ) + const feedItems = feedSkele.map((item) => ({ + itemUri: + typeof item.reason?.repost === 'string' ? item.reason.repost : item.post, + postUri: item.post, + })) return { ...skele, resHeaders, feedItems } } -const skeletonToFeedItems = async ( - skeleton: SkeletonFeedPost[], - ctx: Context, -): Promise => { - const { feedService } = ctx - const feedItemUris = skeleton.map(getSkeleFeedItemUri) - const feedItemsRaw = await feedService.getFeedItems(feedItemUris) - const results: FeedRow[] = [] - for (const skeleItem of skeleton) { - const feedItem = feedItemsRaw[getSkeleFeedItemUri(skeleItem)] - if (feedItem && feedItem.postUri === skeleItem.post) { - results.push(feedItem) - } - } - return results +export type AlgoResponse = { + feedItems: AlgoResponseItem[] + resHeaders?: Record + cursor?: string } -const getSkeleFeedItemUri = (item: SkeletonFeedPost) => { - return typeof item.reason?.repost === 'string' - ? item.reason.repost - : item.post +export type AlgoResponseItem = { + itemUri: string + postUri: string } + +export const toFeedItem = (feedItem: AlgoResponseItem): FeedItem => ({ + post: { uri: feedItem.postUri }, + repost: + feedItem.itemUri === feedItem.postUri + ? undefined + : { uri: feedItem.itemUri }, +}) diff --git a/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts b/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts index 125af1db9b9..57f86af9a28 100644 --- a/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts +++ b/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts @@ -1,11 +1,13 @@ import { InvalidRequestError } from '@atproto/xrpc-server' -import { - DidDocument, - PoorlyFormattedDidDocumentError, - getFeedGen, -} from '@atproto/identity' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { GetIdentityByDidResponse } from '../../../../proto/bsky_pb' +import { + Code, + getServiceEndpoint, + isDataplaneError, + unpackIdentityServices, +} from '../../../../data-plane' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getFeedGenerator({ @@ -14,47 +16,37 @@ export default function (server: Server, ctx: AppContext) { const { feed } = params const viewer = auth.credentials.iss - 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 - let resolved: DidDocument | null + const feedDid = feedInfo.record.did + let identity: GetIdentityByDidResponse try { - resolved = await ctx.idResolver.did.resolve(feedDid) + identity = await ctx.dataplane.getIdentityByDid({ did: feedDid }) } catch (err) { - if (err instanceof PoorlyFormattedDidDocumentError) { - throw new InvalidRequestError(`invalid did document: ${feedDid}`) + if (isDataplaneError(err, Code.NotFound)) { + throw new InvalidRequestError( + `could not resolve identity: ${feedDid}`, + ) } throw err } - if (!resolved) { - throw new InvalidRequestError( - `could not resolve did document: ${feedDid}`, - ) - } - const fgEndpoint = getFeedGen(resolved) + const services = unpackIdentityServices(identity.services) + const fgEndpoint = getServiceEndpoint(services, { + id: 'bsky_fg', + type: 'BskyFeedGenerator', + }) if (!fgEndpoint) { throw new InvalidRequestError( `invalid feed generator service details in did document: ${feedDid}`, ) } - 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') } diff --git a/packages/bsky/src/api/app/bsky/feed/getFeedGenerators.ts b/packages/bsky/src/api/app/bsky/feed/getFeedGenerators.ts index ed6df5760cb..34547948204 100644 --- a/packages/bsky/src/api/app/bsky/feed/getFeedGenerators.ts +++ b/packages/bsky/src/api/app/bsky/feed/getFeedGenerators.ts @@ -1,10 +1,10 @@ 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 { HydrationState, Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' export default function (server: Server, ctx: AppContext) { const getFeedGenerators = createPipeline( @@ -16,17 +16,8 @@ export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getFeedGenerators({ auth: ctx.authVerifier.standardOptional, handler: async ({ params, auth }) => { - const { feeds } = params const viewer = auth.credentials.iss - 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, @@ -35,46 +26,42 @@ 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 => { 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, - ) - return { - ...state, - profiles, - } +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 = (state: HydrationState, ctx: Context) => { - const { feedService } = ctx - const feeds = mapDefined(state.generators, (gen) => - feedService.views.formatFeedGeneratorView(gen, state.profiles), +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 } + return { + feeds, + } } type Context = { - db: Database - feedService: FeedService - actorService: ActorService + hydrator: Hydrator + views: Views } -type Params = { viewer: string | null; feeds: string[] } +type Params = QueryParams & { viewer: string | null } -type SkeletonState = { params: Params; generators: FeedGenInfo[] } - -type HydrationState = SkeletonState & { profiles: ActorInfoMap } +type Skeleton = { + feedUris: string[] +} diff --git a/packages/bsky/src/api/app/bsky/feed/getLikes.ts b/packages/bsky/src/api/app/bsky/feed/getLikes.ts index 2d59656a517..582dd78a073 100644 --- a/packages/bsky/src/api/app/bsky/feed/getLikes.ts +++ b/packages/bsky/src/api/app/bsky/feed/getLikes.ts @@ -1,29 +1,22 @@ import { mapDefined } from '@atproto/common' +import { normalizeDatetimeAlways } from '@atproto/syntax' import { Server } from '../../../../lexicon' import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getLikes' -import { paginate, TimeCidKeyset } from '../../../../db/pagination' import AppContext from '../../../../context' -import { notSoftDeletedClause } from '../../../../db/util' -import { BlockAndMuteState, GraphService } from '../../../../services/graph' -import { ActorInfoMap, ActorService } from '../../../../services/actor' -import { Actor } from '../../../../db/tables/actor' -import { Database } from '../../../../db' import { createPipeline } from '../../../../pipeline' +import { HydrationState, Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { parseString } from '../../../../hydration/util' +import { creatorFromUri } from '../../../../views/util' +import { clearlyBadCursor } from '../../../util' export default function (server: Server, ctx: AppContext) { const getLikes = createPipeline(skeleton, hydration, noBlocks, presentation) server.app.bsky.feed.getLikes({ auth: ctx.authVerifier.standardOptional, handler: async ({ params, auth }) => { - const db = ctx.db.getReplica() - const actorService = ctx.services.actor(db) - const graphService = ctx.services.graph(db) const viewer = auth.credentials.iss - - const result = await getLikes( - { ...params, viewer }, - { db, actorService, graphService }, - ) + const result = await getLikes({ ...params, viewer }, ctx) return { encoding: 'application/json', @@ -33,99 +26,86 @@ export default function (server: Server, ctx: AppContext) { }) } -const skeleton = async ( - params: Params, - ctx: Context, -): Promise => { - const { db } = ctx - const { uri, cid, limit, cursor } = params - const { ref } = db.db.dynamic - - if (TimeCidKeyset.clearlyBad(cursor)) { - return { params, likes: [] } - } - - let builder = db.db - .selectFrom('like') - .where('like.subject', '=', uri) - .innerJoin('actor as creator', 'creator.did', 'like.creator') - .where(notSoftDeletedClause(ref('creator'))) - .selectAll('creator') - .select([ - 'like.cid as cid', - 'like.createdAt as createdAt', - 'like.indexedAt as indexedAt', - 'like.sortAt as sortAt', - ]) - - if (cid) { - builder = builder.where('like.subjectCid', '=', cid) +const skeleton = async (inputs: { + ctx: Context + params: Params +}): Promise => { + const { ctx, params } = inputs + if (clearlyBadCursor(params.cursor)) { + return { likes: [] } } - - const keyset = new TimeCidKeyset(ref('like.sortAt'), ref('like.cid')) - builder = paginate(builder, { - limit, - cursor, - keyset, + const likesRes = await ctx.hydrator.dataplane.getLikesBySubject({ + subject: { uri: params.uri, cid: params.cid }, + cursor: params.cursor, + limit: params.limit, }) - - const likes = await builder.execute() - - return { params, likes, cursor: keyset.packFromResult(likes) } + return { + likes: likesRes.uris, + cursor: parseString(likesRes.cursor), + } } -const hydration = async (state: SkeletonState, ctx: Context) => { - const { graphService, actorService } = ctx - const { params, likes } = state - const { viewer } = params - const [actors, bam] = await Promise.all([ - actorService.views.profiles(likes, viewer), - graphService.getBlockAndMuteState( - viewer ? likes.map((like) => [viewer, like.did]) : [], - ), - ]) - return { ...state, bam, actors } +const hydration = async (inputs: { + ctx: Context + params: Params + skeleton: Skeleton +}) => { + const { ctx, params, skeleton } = inputs + return await ctx.hydrator.hydrateLikes(skeleton.likes, params.viewer) } -const noBlocks = (state: HydrationState) => { - const { viewer } = state.params - if (!viewer) return state - state.likes = state.likes.filter( - (item) => !state.bam.block([viewer, item.did]), - ) - return state +const noBlocks = (inputs: { + ctx: Context + skeleton: Skeleton + hydration: HydrationState +}) => { + const { ctx, skeleton, hydration } = inputs + skeleton.likes = skeleton.likes.filter((uri) => { + const creator = creatorFromUri(uri) + return !ctx.views.viewerBlockExists(creator, hydration) + }) + return skeleton } -const presentation = (state: HydrationState) => { - const { params, likes, actors, cursor } = state - const { uri, cid } = params - const likesView = mapDefined(likes, (like) => - actors[like.did] - ? { - createdAt: like.createdAt, - indexedAt: like.indexedAt, - actor: actors[like.did], - } - : undefined, - ) - return { likes: likesView, cursor, uri, cid } +const presentation = (inputs: { + ctx: Context + params: Params + skeleton: Skeleton + hydration: HydrationState +}) => { + const { ctx, params, skeleton, hydration } = inputs + const likeViews = mapDefined(skeleton.likes, (uri) => { + const like = hydration.likes?.get(uri) + if (!like || !like.record) { + return + } + const creatorDid = creatorFromUri(uri) + const actor = ctx.views.profile(creatorDid, hydration) + if (!actor) { + return + } + return { + actor, + createdAt: normalizeDatetimeAlways(like.record.createdAt), + indexedAt: like.sortedAt.toISOString(), + } + }) + return { + likes: likeViews, + cursor: skeleton.cursor, + uri: params.uri, + cid: params.cid, + } } type Context = { - db: Database - actorService: ActorService - graphService: GraphService + hydrator: Hydrator + views: Views } type Params = QueryParams & { viewer: string | null } -type SkeletonState = { - params: Params - likes: (Actor & { createdAt: string })[] +type Skeleton = { + likes: string[] cursor?: string } - -type HydrationState = SkeletonState & { - bam: BlockAndMuteState - actors: ActorInfoMap -} diff --git a/packages/bsky/src/api/app/bsky/feed/getListFeed.ts b/packages/bsky/src/api/app/bsky/feed/getListFeed.ts index 478d9b08efa..ec182e39ac5 100644 --- a/packages/bsky/src/api/app/bsky/feed/getListFeed.ts +++ b/packages/bsky/src/api/app/bsky/feed/getListFeed.ts @@ -1,18 +1,14 @@ import { Server } from '../../../../lexicon' import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getListFeed' -import { FeedKeyset, getFeedDateThreshold } from '../util/feed' -import { paginate } from '../../../../db/pagination' import AppContext from '../../../../context' -import { setRepoRev } from '../../../util' -import { Database } from '../../../../db' -import { - FeedHydrationState, - FeedRow, - FeedService, -} from '../../../../services/feed' -import { ActorService } from '../../../../services/actor' -import { GraphService } from '../../../../services/graph' +import { clearlyBadCursor, setRepoRev } from '../../../util' import { createPipeline } from '../../../../pipeline' +import { HydrationState, Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { DataPlaneClient } from '../../../../data-plane' +import { mapDefined } from '@atproto/common' +import { parseString } from '../../../../hydration/util' +import { FeedItem } from '../../../../hydration/feed' export default function (server: Server, ctx: AppContext) { const getListFeed = createPipeline( @@ -25,19 +21,10 @@ export default function (server: Server, ctx: AppContext) { auth: ctx.authVerifier.standardOptional, handler: async ({ params, auth, res }) => { const viewer = auth.credentials.iss - const db = ctx.db.getReplica() - const actorService = ctx.services.actor(db) - const feedService = ctx.services.feed(db) - const graphService = ctx.services.graph(db) - const [result, repoRev] = await Promise.all([ - getListFeed( - { ...params, viewer }, - { db, actorService, feedService, graphService }, - ), - actorService.getRepoRev(viewer), - ]) + const result = await getListFeed({ ...params, viewer }, ctx) + const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) setRepoRev(res, repoRev) return { @@ -48,84 +35,78 @@ export default function (server: Server, ctx: AppContext) { }) } -export const skeleton = async ( - params: Params, - ctx: Context, -): Promise => { - const { list, cursor, limit } = params - const { db } = ctx - const { ref } = db.db.dynamic - - if (FeedKeyset.clearlyBad(cursor)) { - return { params, feedItems: [] } +export const skeleton = async (inputs: { + ctx: Context + params: Params +}): Promise => { + const { ctx, params } = inputs + if (clearlyBadCursor(params.cursor)) { + return { items: [] } } - - const keyset = new FeedKeyset(ref('post.sortAt'), ref('post.cid')) - const sortFrom = keyset.unpack(cursor)?.primary - - let builder = ctx.feedService - .selectPostQb() - .innerJoin('list_item', 'list_item.subjectDid', 'post.creator') - .where('list_item.listUri', '=', list) - .where('post.sortAt', '>', getFeedDateThreshold(sortFrom, 3)) - - builder = paginate(builder, { - limit, - cursor, - keyset, - tryIndex: true, + const res = await ctx.dataplane.getListFeed({ + listUri: params.list, + limit: params.limit, + cursor: params.cursor, }) - const feedItems = await builder.execute() - return { - params, - feedItems, - cursor: keyset.packFromResult(feedItems), + items: res.items.map((item) => ({ + post: { uri: item.uri, cid: item.cid || undefined }, + repost: item.repost + ? { uri: item.repost, cid: item.repostCid || undefined } + : undefined, + })), + cursor: parseString(res.cursor), } } -const hydration = async (state: SkeletonState, ctx: Context) => { - const { feedService } = ctx - const { params, feedItems } = state - const refs = feedService.feedItemRefs(feedItems) - const hydrated = await feedService.feedHydration({ - ...refs, - viewer: params.viewer, - }) - return { ...state, ...hydrated } +const hydration = async (inputs: { + ctx: Context + params: Params + skeleton: Skeleton +}): Promise => { + const { ctx, params, skeleton } = inputs + return ctx.hydrator.hydrateFeedItems(skeleton.items, params.viewer) } -const noBlocksOrMutes = (state: HydrationState) => { - const { viewer } = state.params - if (!viewer) return state - state.feedItems = state.feedItems.filter( - (item) => - !state.bam.block([viewer, item.postAuthorDid]) && - !state.bam.mute([viewer, item.postAuthorDid]), - ) - return state +const noBlocksOrMutes = (inputs: { + ctx: Context + skeleton: Skeleton + hydration: HydrationState +}): Skeleton => { + const { ctx, skeleton, hydration } = inputs + skeleton.items = skeleton.items.filter((item) => { + const bam = ctx.views.feedItemBlocksAndMutes(item, hydration) + return ( + !bam.authorBlocked && + !bam.authorMuted && + !bam.originatorBlocked && + !bam.originatorMuted + ) + }) + return skeleton } -const presentation = (state: HydrationState, ctx: Context) => { - const { feedService } = ctx - const { feedItems, cursor, params } = state - const feed = feedService.views.formatFeed(feedItems, state, params.viewer) - return { feed, cursor } +const presentation = (inputs: { + ctx: Context + skeleton: Skeleton + hydration: HydrationState +}) => { + const { ctx, skeleton, hydration } = inputs + const feed = mapDefined(skeleton.items, (item) => + ctx.views.feedViewPost(item, hydration), + ) + return { feed, cursor: skeleton.cursor } } type Context = { - db: Database - actorService: ActorService - feedService: FeedService - graphService: GraphService + hydrator: Hydrator + views: Views + dataplane: DataPlaneClient } type Params = QueryParams & { viewer: string | null } -type SkeletonState = { - params: Params - feedItems: FeedRow[] +type Skeleton = { + items: FeedItem[] cursor?: string } - -type HydrationState = SkeletonState & FeedHydrationState diff --git a/packages/bsky/src/api/app/bsky/feed/getPostThread.ts b/packages/bsky/src/api/app/bsky/feed/getPostThread.ts index 18d9d3124d0..cfe59e290fb 100644 --- a/packages/bsky/src/api/app/bsky/feed/getPostThread.ts +++ b/packages/bsky/src/api/app/bsky/feed/getPostThread.ts @@ -1,27 +1,22 @@ import { InvalidRequestError } from '@atproto/xrpc-server' -import { AtUri } from '@atproto/syntax' import { Server } from '../../../../lexicon' +import { isNotFoundPost } from '../../../../lexicon/types/app/bsky/feed/defs' import { - BlockedPost, - NotFoundPost, - ThreadViewPost, - isNotFoundPost, -} from '../../../../lexicon/types/app/bsky/feed/defs' -import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getPostThread' + QueryParams, + OutputSchema, +} from '../../../../lexicon/types/app/bsky/feed/getPostThread' import AppContext from '../../../../context' -import { - FeedService, - FeedRow, - FeedHydrationState, -} from '../../../../services/feed' -import { - getAncestorsAndSelfQb, - getDescendentsQb, -} from '../../../../services/util/post' -import { Database } from '../../../../db' import { setRepoRev } from '../../../util' -import { ActorInfoMap, ActorService } from '../../../../services/actor' -import { createPipeline, noRules } from '../../../../pipeline' +import { + HydrationFnInput, + PresentationFnInput, + SkeletonFnInput, + createPipeline, + noRules, +} from '../../../../pipeline' +import { Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { DataPlaneClient, isDataplaneError, Code } from '../../../../data-plane' export default function (server: Server, ctx: AppContext) { const getPostThread = createPipeline( @@ -34,299 +29,85 @@ export default function (server: Server, ctx: AppContext) { auth: ctx.authVerifier.optionalStandardOrRole, handler: async ({ params, auth, res }) => { const { viewer } = ctx.authVerifier.parseCreds(auth) - const db = ctx.db.getReplica('thread') - const feedService = ctx.services.feed(db) - const actorService = ctx.services.actor(db) - const [result, repoRev] = await Promise.allSettled([ - getPostThread({ ...params, viewer }, { db, feedService, actorService }), - actorService.getRepoRev(viewer), - ]) - - if (repoRev.status === 'fulfilled') { - setRepoRev(res, repoRev.value) - } - if (result.status === 'rejected') { - throw result.reason + let result: OutputSchema + try { + result = await getPostThread({ ...params, viewer }, ctx) + } catch (err) { + const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) + setRepoRev(res, repoRev) + throw err } + const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) + setRepoRev(res, repoRev) + return { encoding: 'application/json', - body: result.value, + body: result, } }, }) } -const skeleton = async (params: Params, ctx: Context) => { - const threadData = await getThreadData(params, ctx) - if (!threadData) { - throw new InvalidRequestError(`Post not found: ${params.uri}`, 'NotFound') - } - return { params, threadData } -} - -const hydration = async (state: SkeletonState, ctx: Context) => { - const { feedService } = ctx - const { - threadData, - params: { viewer }, - } = state - const relevant = getRelevantIds(threadData) - const hydrated = await feedService.feedHydration({ ...relevant, viewer }) - return { ...state, ...hydrated } -} - -const presentation = (state: HydrationState, ctx: Context) => { - const { params, profiles } = state - const { actorService } = ctx - const actors = actorService.views.profileBasicPresentation( - Object.keys(profiles), - state, - params.viewer, - ) - const thread = composeThread( - state.threadData, - actors, - state, - ctx, - params.viewer, - ) - if (isNotFoundPost(thread)) { - // @TODO technically this could be returned as a NotFoundPost based on lexicon - throw new InvalidRequestError(`Post not found: ${params.uri}`, 'NotFound') - } - return { thread } -} - -const composeThread = ( - threadData: PostThread, - actors: ActorInfoMap, - state: HydrationState, - ctx: Context, - viewer: string | null, -) => { - const { feedService } = ctx - const { posts, threadgates, embeds, blocks, labels, lists } = state - - const post = feedService.views.formatPostView( - threadData.post.postUri, - actors, - posts, - threadgates, - embeds, - labels, - lists, - viewer, - ) - - // replies that are invalid due to reply-gating: - // a. may appear as the anchor post, but without any parent or replies. - // b. may not appear anywhere else in the thread. - const isAnchorPost = state.threadData.post.uri === threadData.post.postUri - const info = posts[threadData.post.postUri] - // @TODO re-enable invalidReplyRoot check - // const badReply = !!info?.invalidReplyRoot || !!info?.violatesThreadGate - const badReply = !!info?.violatesThreadGate - const violatesBlock = (post && blocks[post.uri]?.reply) ?? false - const omitBadReply = !isAnchorPost && (badReply || violatesBlock) - - if (!post || omitBadReply) { - return { - $type: 'app.bsky.feed.defs#notFoundPost', - uri: threadData.post.postUri, - notFound: true, - } - } - - if (post.author.viewer?.blocking || post.author.viewer?.blockedBy) { +const skeleton = async (inputs: SkeletonFnInput) => { + const { ctx, params } = inputs + try { + const res = await ctx.dataplane.getThread({ + postUri: params.uri, + above: params.parentHeight, + below: params.depth, + }) return { - $type: 'app.bsky.feed.defs#blockedPost', - uri: threadData.post.postUri, - blocked: true, - author: { - did: post.author.did, - viewer: post.author.viewer - ? { - blockedBy: post.author.viewer?.blockedBy, - blocking: post.author.viewer?.blocking, - } - : undefined, - }, + anchor: params.uri, + uris: res.uris, } - } - - let parent - if (threadData.parent && !badReply && !violatesBlock) { - if (threadData.parent instanceof ParentNotFoundError) { - parent = { - $type: 'app.bsky.feed.defs#notFoundPost', - uri: threadData.parent.uri, - notFound: true, + } catch (err) { + if (isDataplaneError(err, Code.NotFound)) { + return { + anchor: params.uri, + uris: [], } } else { - parent = composeThread(threadData.parent, actors, state, ctx, viewer) + throw err } } - - let replies: (ThreadViewPost | NotFoundPost | BlockedPost)[] | undefined - if (threadData.replies && !badReply) { - replies = threadData.replies.flatMap((reply) => { - const thread = composeThread(reply, actors, state, ctx, viewer) - // e.g. don't bother including #postNotFound reply placeholders for takedowns. either way matches api contract. - const skip = [] - return isNotFoundPost(thread) ? skip : thread - }) - } - - return { - $type: 'app.bsky.feed.defs#threadViewPost', - post, - parent, - replies, - } } -const getRelevantIds = ( - thread: PostThread, -): { dids: Set; uris: Set } => { - const dids = new Set() - const uris = new Set() - if (thread.parent && !(thread.parent instanceof ParentNotFoundError)) { - const fromParent = getRelevantIds(thread.parent) - fromParent.dids.forEach((did) => dids.add(did)) - fromParent.uris.forEach((uri) => uris.add(uri)) - } - if (thread.replies) { - for (const reply of thread.replies) { - const fromChild = getRelevantIds(reply) - fromChild.dids.forEach((did) => dids.add(did)) - fromChild.uris.forEach((uri) => uris.add(uri)) - } - } - dids.add(thread.post.postAuthorDid) - uris.add(thread.post.postUri) - if (thread.post.replyRoot) { - // ensure root is included for checking interactions - uris.add(thread.post.replyRoot) - dids.add(new AtUri(thread.post.replyRoot).hostname) - } - return { dids, uris } -} - -const getThreadData = async ( - params: Params, - ctx: Context, -): Promise => { - const { db, feedService } = ctx - const { uri, depth, parentHeight } = params - - const [parents, children] = await Promise.all([ - getAncestorsAndSelfQb(db.db, { uri, parentHeight }) - .selectFrom('ancestor') - .innerJoin( - feedService.selectPostQb().as('post'), - 'post.uri', - 'ancestor.uri', - ) - .selectAll('post') - .execute(), - getDescendentsQb(db.db, { uri, depth }) - .selectFrom('descendent') - .innerJoin( - feedService.selectPostQb().as('post'), - 'post.uri', - 'descendent.uri', - ) - .selectAll('post') - .orderBy('sortAt', 'desc') - .execute(), - ]) - // prevent self-referential loops - const includedPosts = new Set([uri]) - const parentsByUri = parents.reduce((acc, post) => { - return Object.assign(acc, { [post.uri]: post }) - }, {} as Record) - const childrenByParentUri = children.reduce((acc, child) => { - if (!child.replyParent) return acc - if (includedPosts.has(child.uri)) return acc - includedPosts.add(child.uri) - acc[child.replyParent] ??= [] - acc[child.replyParent].push(child) - return acc - }, {} as Record) - const post = parentsByUri[uri] - if (!post) return null - return { - post, - parent: post.replyParent - ? getParentData( - parentsByUri, - includedPosts, - post.replyParent, - parentHeight, - ) - : undefined, - replies: getChildrenData(childrenByParentUri, uri, depth), - } -} - -const getParentData = ( - postsByUri: Record, - includedPosts: Set, - uri: string, - depth: number, -): PostThread | ParentNotFoundError | undefined => { - if (depth < 1) return undefined - if (includedPosts.has(uri)) return undefined - includedPosts.add(uri) - const post = postsByUri[uri] - if (!post) return new ParentNotFoundError(uri) - return { - post, - parent: post.replyParent - ? getParentData(postsByUri, includedPosts, post.replyParent, depth - 1) - : undefined, - replies: [], - } -} - -const getChildrenData = ( - childrenByParentUri: Record, - uri: string, - depth: number, -): PostThread[] | undefined => { - if (depth === 0) return undefined - const children = childrenByParentUri[uri] ?? [] - return children.map((row) => ({ - post: row, - replies: getChildrenData(childrenByParentUri, row.postUri, depth - 1), - })) +const hydration = async ( + inputs: HydrationFnInput, +) => { + const { ctx, params, skeleton } = inputs + return ctx.hydrator.hydrateThreadPosts( + skeleton.uris.map((uri) => ({ uri })), + params.viewer, + ) } -class ParentNotFoundError extends Error { - constructor(public uri: string) { - super(`Parent not found: ${uri}`) +const presentation = ( + inputs: PresentationFnInput, +) => { + const { ctx, params, skeleton, hydration } = inputs + const thread = ctx.views.thread(skeleton, hydration, { + height: params.parentHeight, + depth: params.depth, + }) + if (isNotFoundPost(thread)) { + // @TODO technically this could be returned as a NotFoundPost based on lexicon + throw new InvalidRequestError(`Post not found: ${params.uri}`, 'NotFound') } -} - -type PostThread = { - post: FeedRow - parent?: PostThread | ParentNotFoundError - replies?: PostThread[] + return { thread } } type Context = { - db: Database - feedService: FeedService - actorService: ActorService + dataplane: DataPlaneClient + hydrator: Hydrator + views: Views } type Params = QueryParams & { viewer: string | null } -type SkeletonState = { - params: Params - threadData: PostThread +type Skeleton = { + anchor: string + uris: string[] } - -type HydrationState = SkeletonState & FeedHydrationState diff --git a/packages/bsky/src/api/app/bsky/feed/getPosts.ts b/packages/bsky/src/api/app/bsky/feed/getPosts.ts index 9db7cf0a252..fc9592ad7f7 100644 --- a/packages/bsky/src/api/app/bsky/feed/getPosts.ts +++ b/packages/bsky/src/api/app/bsky/feed/getPosts.ts @@ -1,30 +1,19 @@ -import { dedupeStrs } from '@atproto/common' +import { dedupeStrs, mapDefined } from '@atproto/common' import { Server } from '../../../../lexicon' import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getPosts' import AppContext from '../../../../context' -import { Database } from '../../../../db' -import { - FeedHydrationState, - FeedRow, - FeedService, -} from '../../../../services/feed' import { createPipeline } from '../../../../pipeline' -import { ActorService } from '../../../../services/actor' +import { HydrationState, Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { creatorFromUri } from '../../../../views/util' export default function (server: Server, ctx: AppContext) { const getPosts = createPipeline(skeleton, hydration, noBlocks, presentation) server.app.bsky.feed.getPosts({ auth: ctx.authVerifier.standardOptional, handler: async ({ params, auth }) => { - const db = ctx.db.getReplica() - const feedService = ctx.services.feed(db) - const actorService = ctx.services.actor(db) const viewer = auth.credentials.iss - - const results = await getPosts( - { ...params, viewer }, - { db, feedService, actorService }, - ) + const results = await getPosts({ ...params, viewer }, ctx) return { encoding: 'application/json', @@ -34,68 +23,55 @@ export default function (server: Server, ctx: AppContext) { }) } -const skeleton = async (params: Params, ctx: Context) => { - const deduped = dedupeStrs(params.uris) - const feedItems = await ctx.feedService.postUrisToFeedItems(deduped) - return { params, feedItems } +const skeleton = async (inputs: { params: Params }) => { + return { posts: dedupeStrs(inputs.params.uris) } } -const hydration = async (state: SkeletonState, ctx: Context) => { - const { feedService } = ctx - const { params, feedItems } = state - const refs = feedService.feedItemRefs(feedItems) - const hydrated = await feedService.feedHydration({ - ...refs, - viewer: params.viewer, - }) - return { ...state, ...hydrated } +const hydration = async (inputs: { + ctx: Context + params: Params + skeleton: Skeleton +}) => { + const { ctx, params, skeleton } = inputs + return ctx.hydrator.hydratePosts( + skeleton.posts.map((uri) => ({ uri })), + params.viewer, + ) } -const noBlocks = (state: HydrationState) => { - const { viewer } = state.params - state.feedItems = state.feedItems.filter((item) => { - if (!viewer) return true - return !state.bam.block([viewer, item.postAuthorDid]) +const noBlocks = (inputs: { + ctx: Context + skeleton: Skeleton + hydration: HydrationState +}) => { + const { ctx, skeleton, hydration } = inputs + skeleton.posts = skeleton.posts.filter((uri) => { + const creator = creatorFromUri(uri) + return !ctx.views.viewerBlockExists(creator, hydration) }) - return state + return skeleton } -const presentation = (state: HydrationState, ctx: Context) => { - const { feedService, actorService } = ctx - const { feedItems, profiles, params } = state - const SKIP = [] - const actors = actorService.views.profileBasicPresentation( - Object.keys(profiles), - state, - params.viewer, +const presentation = (inputs: { + ctx: Context + params: Params + skeleton: Skeleton + hydration: HydrationState +}) => { + const { ctx, skeleton, hydration } = inputs + const posts = mapDefined(skeleton.posts, (uri) => + ctx.views.post(uri, hydration), ) - const postViews = feedItems.flatMap((item) => { - const postView = feedService.views.formatPostView( - item.postUri, - actors, - state.posts, - state.threadgates, - state.embeds, - state.labels, - state.lists, - params.viewer, - ) - return postView ?? SKIP - }) - return { posts: postViews } + return { posts } } type Context = { - db: Database - feedService: FeedService - actorService: ActorService + hydrator: Hydrator + views: Views } type Params = QueryParams & { viewer: string | null } -type SkeletonState = { - params: Params - feedItems: FeedRow[] +type Skeleton = { + posts: string[] } - -type HydrationState = SkeletonState & FeedHydrationState diff --git a/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts b/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts index 7d28014a7b7..fe33e305774 100644 --- a/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts +++ b/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts @@ -1,14 +1,13 @@ import { mapDefined } from '@atproto/common' import { Server } from '../../../../lexicon' import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getRepostedBy' -import { paginate, TimeCidKeyset } from '../../../../db/pagination' import AppContext from '../../../../context' -import { notSoftDeletedClause } from '../../../../db/util' -import { Database } from '../../../../db' -import { ActorInfoMap, ActorService } from '../../../../services/actor' -import { BlockAndMuteState, GraphService } from '../../../../services/graph' -import { Actor } from '../../../../db/tables/actor' import { createPipeline } from '../../../../pipeline' +import { HydrationState, Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { parseString } from '../../../../hydration/util' +import { creatorFromUri } from '../../../../views/util' +import { clearlyBadCursor } from '../../../util' export default function (server: Server, ctx: AppContext) { const getRepostedBy = createPipeline( @@ -20,15 +19,8 @@ export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getRepostedBy({ auth: ctx.authVerifier.standardOptional, handler: async ({ params, auth }) => { - const db = ctx.db.getReplica() - const actorService = ctx.services.actor(db) - const graphService = ctx.services.graph(db) const viewer = auth.credentials.iss - - const result = await getRepostedBy( - { ...params, viewer }, - { db, actorService, graphService }, - ) + const result = await getRepostedBy({ ...params, viewer }, ctx) return { encoding: 'application/json', @@ -38,85 +30,78 @@ export default function (server: Server, ctx: AppContext) { }) } -const skeleton = async ( - params: Params, - ctx: Context, -): Promise => { - const { db } = ctx - const { limit, cursor, uri, cid } = params - const { ref } = db.db.dynamic - - if (TimeCidKeyset.clearlyBad(cursor)) { - return { params, repostedBy: [] } - } - - let builder = db.db - .selectFrom('repost') - .where('repost.subject', '=', uri) - .innerJoin('actor as creator', 'creator.did', 'repost.creator') - .where(notSoftDeletedClause(ref('creator'))) - .selectAll('creator') - .select(['repost.cid as cid', 'repost.sortAt as sortAt']) - - if (cid) { - builder = builder.where('repost.subjectCid', '=', cid) +const skeleton = async (inputs: { + ctx: Context + params: Params +}): Promise => { + const { ctx, params } = inputs + if (clearlyBadCursor(params.cursor)) { + return { reposts: [] } } - - const keyset = new TimeCidKeyset(ref('repost.sortAt'), ref('repost.cid')) - builder = paginate(builder, { - limit, - cursor, - keyset, + const res = await ctx.hydrator.dataplane.getRepostsBySubject({ + subject: { uri: params.uri, cid: params.cid }, + cursor: params.cursor, + limit: params.limit, }) - - const repostedBy = await builder.execute() - return { params, repostedBy, cursor: keyset.packFromResult(repostedBy) } + return { + reposts: res.uris, + cursor: parseString(res.cursor), + } } -const hydration = async (state: SkeletonState, ctx: Context) => { - const { graphService, actorService } = ctx - const { params, repostedBy } = state - const { viewer } = params - const [actors, bam] = await Promise.all([ - actorService.views.profiles(repostedBy, viewer), - graphService.getBlockAndMuteState( - viewer ? repostedBy.map((item) => [viewer, item.did]) : [], - ), - ]) - return { ...state, bam, actors } +const hydration = async (inputs: { + ctx: Context + params: Params + skeleton: Skeleton +}) => { + const { ctx, params, skeleton } = inputs + return await ctx.hydrator.hydrateReposts(skeleton.reposts, params.viewer) } -const noBlocks = (state: HydrationState) => { - const { viewer } = state.params - if (!viewer) return state - state.repostedBy = state.repostedBy.filter( - (item) => !state.bam.block([viewer, item.did]), - ) - return state +const noBlocks = (inputs: { + ctx: Context + skeleton: Skeleton + hydration: HydrationState +}) => { + const { ctx, skeleton, hydration } = inputs + skeleton.reposts = skeleton.reposts.filter((uri) => { + const creator = creatorFromUri(uri) + return !ctx.views.viewerBlockExists(creator, hydration) + }) + return skeleton } -const presentation = (state: HydrationState) => { - const { params, repostedBy, actors, cursor } = state - const { uri, cid } = params - const repostedByView = mapDefined(repostedBy, (item) => actors[item.did]) - return { repostedBy: repostedByView, cursor, uri, cid } +const presentation = (inputs: { + ctx: Context + params: Params + skeleton: Skeleton + hydration: HydrationState +}) => { + const { ctx, params, skeleton, hydration } = inputs + const repostViews = mapDefined(skeleton.reposts, (uri) => { + const repost = hydration.reposts?.get(uri) + if (!repost?.record) { + return + } + const creatorDid = creatorFromUri(uri) + return ctx.views.profile(creatorDid, hydration) + }) + return { + repostedBy: repostViews, + cursor: skeleton.cursor, + uri: params.uri, + cid: params.cid, + } } type Context = { - db: Database - actorService: ActorService - graphService: GraphService + hydrator: Hydrator + views: Views } type Params = QueryParams & { viewer: string | null } -type SkeletonState = { - params: Params - repostedBy: Actor[] +type Skeleton = { + reposts: string[] cursor?: string } - -type HydrationState = SkeletonState & { - bam: BlockAndMuteState - actors: ActorInfoMap -} diff --git a/packages/bsky/src/api/app/bsky/feed/getSuggestedFeeds.ts b/packages/bsky/src/api/app/bsky/feed/getSuggestedFeeds.ts index b96ae2722fa..0524f33b0c0 100644 --- a/packages/bsky/src/api/app/bsky/feed/getSuggestedFeeds.ts +++ b/packages/bsky/src/api/app/bsky/feed/getSuggestedFeeds.ts @@ -1,37 +1,31 @@ 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.authVerifier.standardOptional, - handler: async ({ auth }) => { - // @NOTE ignores cursor, doesn't matter for appview swap + handler: async ({ auth, params }) => { const viewer = auth.credentials.iss - 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), + // @NOTE no need to coordinate the cursor for appview swap, as v1 doesn't use the cursor + 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), }, } }, diff --git a/packages/bsky/src/api/app/bsky/feed/getTimeline.ts b/packages/bsky/src/api/app/bsky/feed/getTimeline.ts index 05ef505ea04..e7e4bed20a9 100644 --- a/packages/bsky/src/api/app/bsky/feed/getTimeline.ts +++ b/packages/bsky/src/api/app/bsky/feed/getTimeline.ts @@ -1,18 +1,14 @@ -import { sql } from 'kysely' -import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' -import { FeedAlgorithm, FeedKeyset, getFeedDateThreshold } from '../util/feed' -import { paginate } from '../../../../db/pagination' import AppContext from '../../../../context' -import { Database } from '../../../../db' import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/getTimeline' -import { setRepoRev } from '../../../util' -import { - FeedHydrationState, - FeedRow, - FeedService, -} from '../../../../services/feed' +import { clearlyBadCursor, setRepoRev } from '../../../util' import { createPipeline } from '../../../../pipeline' +import { HydrationState, Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { DataPlaneClient } from '../../../../data-plane' +import { parseString } from '../../../../hydration/util' +import { mapDefined } from '@atproto/common' +import { FeedItem } from '../../../../hydration/feed' export default function (server: Server, ctx: AppContext) { const getTimeline = createPipeline( @@ -25,15 +21,10 @@ export default function (server: Server, ctx: AppContext) { auth: ctx.authVerifier.standard, handler: async ({ params, auth, res }) => { const viewer = auth.credentials.iss - const db = ctx.db.getReplica('timeline') - const feedService = ctx.services.feed(db) - const actorService = ctx.services.actor(db) - const [result, repoRev] = await Promise.all([ - getTimeline({ ...params, viewer }, { db, feedService }), - actorService.getRepoRev(viewer), - ]) + const result = await getTimeline({ ...params, viewer }, ctx) + const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) setRepoRev(res, repoRev) return { @@ -44,181 +35,78 @@ export default function (server: Server, ctx: AppContext) { }) } -export const skeleton = async ( - params: Params, - ctx: Context, -): Promise => { - const { cursor, limit, algorithm, viewer } = params - const { db } = ctx - const { ref } = db.db.dynamic - - if (algorithm && algorithm !== FeedAlgorithm.ReverseChronological) { - throw new InvalidRequestError(`Unsupported algorithm: ${algorithm}`) - } - - if (limit === 1 && !cursor) { - // special case for limit=1, which is often used to check if there are new items at the top of the timeline. - return skeletonLimit1(params, ctx) - } - - if (FeedKeyset.clearlyBad(cursor)) { - return { params, feedItems: [] } +export const skeleton = async (inputs: { + ctx: Context + params: Params +}): Promise => { + const { ctx, params } = inputs + if (clearlyBadCursor(params.cursor)) { + return { items: [] } } - - const keyset = new FeedKeyset(ref('feed_item.sortAt'), ref('feed_item.cid')) - const sortFrom = keyset.unpack(cursor)?.primary - - let followQb = db.db - .selectFrom('feed_item') - .innerJoin('follow', 'follow.subjectDid', 'feed_item.originatorDid') - .where('follow.creator', '=', viewer) - .innerJoin('post', 'post.uri', 'feed_item.postUri') - .where('feed_item.sortAt', '>', getFeedDateThreshold(sortFrom, 2)) - .selectAll('feed_item') - .select([ - 'post.replyRoot', - 'post.replyParent', - 'post.creator as postAuthorDid', - ]) - - followQb = paginate(followQb, { - limit, - cursor, - keyset, - tryIndex: true, - }) - - let selfQb = db.db - .selectFrom('feed_item') - .innerJoin('post', 'post.uri', 'feed_item.postUri') - .where('feed_item.originatorDid', '=', viewer) - .where('feed_item.sortAt', '>', getFeedDateThreshold(sortFrom, 2)) - .selectAll('feed_item') - .select([ - 'post.replyRoot', - 'post.replyParent', - 'post.creator as postAuthorDid', - ]) - - selfQb = paginate(selfQb, { - limit: Math.min(limit, 10), - cursor, - keyset, - tryIndex: true, + const res = await ctx.dataplane.getTimeline({ + actorDid: params.viewer, + limit: params.limit, + cursor: params.cursor, }) - - const [followRes, selfRes] = await Promise.all([ - followQb.execute(), - selfQb.execute(), - ]) - - const feedItems: FeedRow[] = [...followRes, ...selfRes] - .sort((a, b) => { - if (a.sortAt > b.sortAt) return -1 - if (a.sortAt < b.sortAt) return 1 - return a.cid > b.cid ? -1 : 1 - }) - .slice(0, limit) - return { - params, - feedItems, - cursor: keyset.packFromResult(feedItems), + items: res.items.map((item) => ({ + post: { uri: item.uri, cid: item.cid || undefined }, + repost: item.repost + ? { uri: item.repost, cid: item.repostCid || undefined } + : undefined, + })), + cursor: parseString(res.cursor), } } -// The limit=1 case is used commonly to check if there are new items at the top of the timeline. -// Since it's so common, it's optimized here. The most common strategy that postgres takes to -// build a timeline is to grab all recent content from each of the user's follow, then paginate it. -// The downside here is that it requires grabbing all recent content from all follows, even if you -// only want a single result. The approach here instead takes the single most recent post from -// each of the user's follows, then sorts only those and takes the top item. -const skeletonLimit1 = async (params: Params, ctx: Context) => { - const { viewer } = params - const { db } = ctx - const { ref } = db.db.dynamic - const creatorsQb = db.db - .selectFrom('follow') - .where('creator', '=', viewer) - .select('subjectDid as did') - .unionAll(sql`select ${viewer} as did`) - const feedItemsQb = db.db - .selectFrom(creatorsQb.as('creator')) - .innerJoinLateral( - (eb) => { - const keyset = new FeedKeyset( - ref('feed_item.sortAt'), - ref('feed_item.cid'), - ) - const creatorFeedItemQb = eb - .selectFrom('feed_item') - .innerJoin('post', 'post.uri', 'feed_item.postUri') - .whereRef('feed_item.originatorDid', '=', 'creator.did') - .where('feed_item.sortAt', '>', getFeedDateThreshold(undefined, 2)) - .selectAll('feed_item') - .select([ - 'post.replyRoot', - 'post.replyParent', - 'post.creator as postAuthorDid', - ]) - return paginate(creatorFeedItemQb, { limit: 1, keyset }).as('result') - }, - (join) => join.onTrue(), - ) - .selectAll('result') - const keyset = new FeedKeyset(ref('result.sortAt'), ref('result.cid')) - const feedItems = await paginate(feedItemsQb, { limit: 1, keyset }).execute() - return { - params, - feedItems, - cursor: keyset.packFromResult(feedItems), - } +const hydration = async (inputs: { + ctx: Context + params: Params + skeleton: Skeleton +}): Promise => { + const { ctx, params, skeleton } = inputs + return ctx.hydrator.hydrateFeedItems(skeleton.items, params.viewer) } -const hydration = async ( - state: SkeletonState, - ctx: Context, -): Promise => { - const { feedService } = ctx - const { params, feedItems } = state - const refs = feedService.feedItemRefs(feedItems) - const hydrated = await feedService.feedHydration({ - ...refs, - viewer: params.viewer, +const noBlocksOrMutes = (inputs: { + ctx: Context + skeleton: Skeleton + hydration: HydrationState +}): Skeleton => { + const { ctx, skeleton, hydration } = inputs + skeleton.items = skeleton.items.filter((item) => { + const bam = ctx.views.feedItemBlocksAndMutes(item, hydration) + return ( + !bam.authorBlocked && + !bam.authorMuted && + !bam.originatorBlocked && + !bam.originatorMuted + ) }) - return { ...state, ...hydrated } + return skeleton } -const noBlocksOrMutes = (state: HydrationState): HydrationState => { - const { viewer } = state.params - state.feedItems = state.feedItems.filter( - (item) => - !state.bam.block([viewer, item.postAuthorDid]) && - !state.bam.block([viewer, item.originatorDid]) && - !state.bam.mute([viewer, item.postAuthorDid]) && - !state.bam.mute([viewer, item.originatorDid]), +const presentation = (inputs: { + ctx: Context + skeleton: Skeleton + hydration: HydrationState +}) => { + const { ctx, skeleton, hydration } = inputs + const feed = mapDefined(skeleton.items, (item) => + ctx.views.feedViewPost(item, hydration), ) - return state -} - -const presentation = (state: HydrationState, ctx: Context) => { - const { feedService } = ctx - const { feedItems, cursor, params } = state - const feed = feedService.views.formatFeed(feedItems, state, params.viewer) - return { feed, cursor } + return { feed, cursor: skeleton.cursor } } type Context = { - db: Database - feedService: FeedService + hydrator: Hydrator + views: Views + dataplane: DataPlaneClient } type Params = QueryParams & { viewer: string } -type SkeletonState = { - params: Params - feedItems: FeedRow[] +type Skeleton = { + items: FeedItem[] cursor?: string } - -type HydrationState = SkeletonState & FeedHydrationState diff --git a/packages/bsky/src/api/app/bsky/feed/searchPosts.ts b/packages/bsky/src/api/app/bsky/feed/searchPosts.ts index 3ac0b3f9477..876b3113b9f 100644 --- a/packages/bsky/src/api/app/bsky/feed/searchPosts.ts +++ b/packages/bsky/src/api/app/bsky/feed/searchPosts.ts @@ -1,17 +1,20 @@ import AppContext from '../../../../context' import { Server } from '../../../../lexicon' -import { InvalidRequestError } from '@atproto/xrpc-server' import AtpAgent from '@atproto/api' import { mapDefined } from '@atproto/common' import { QueryParams } from '../../../../lexicon/types/app/bsky/feed/searchPosts' -import { Database } from '../../../../db' import { - FeedHydrationState, - FeedRow, - FeedService, -} from '../../../../services/feed' -import { ActorService } from '../../../../services/actor' -import { createPipeline } from '../../../../pipeline' + HydrationFnInput, + PresentationFnInput, + RulesFnInput, + SkeletonFnInput, + createPipeline, +} from '../../../../pipeline' +import { Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { DataPlaneClient } from '../../../../data-plane' +import { parseString } from '../../../../hydration/util' +import { creatorFromUri } from '../../../../views/util' export default function (server: Server, ctx: AppContext) { const searchPosts = createPipeline( @@ -24,19 +27,7 @@ export default function (server: Server, ctx: AppContext) { auth: ctx.authVerifier.standardOptional, handler: async ({ auth, params }) => { const viewer = auth.credentials.iss - 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 }, - ) - + const results = await searchPosts({ ...params, viewer }, ctx) return { encoding: 'application/json', body: results, @@ -45,87 +36,78 @@ export default function (server: Server, ctx: AppContext) { }) } -const skeleton = async ( - params: Params, - ctx: Context, -): Promise => { - // @NOTE cursors wont change on appview swap - const res = await ctx.searchAgent.api.app.bsky.unspecced.searchPostsSkeleton({ - q: params.q, - cursor: params.cursor, +const skeleton = async (inputs: SkeletonFnInput) => { + const { ctx, params } = inputs + + if (ctx.searchAgent) { + // @NOTE cursors wont change on appview swap + const { data: res } = + await ctx.searchAgent.api.app.bsky.unspecced.searchPostsSkeleton({ + q: params.q, + cursor: params.cursor, + limit: params.limit, + }) + return { + posts: res.posts.map(({ uri }) => uri), + cursor: parseString(res.cursor), + } + } + + const res = await ctx.dataplane.searchPosts({ + term: params.q, limit: params.limit, + cursor: params.cursor, }) - const postUris = res.data.posts.map((a) => a.uri) - const feedItems = await ctx.feedService.postUrisToFeedItems(postUris) return { - params, - feedItems, - cursor: res.data.cursor, - hitsTotal: res.data.hitsTotal, + posts: res.uris, + cursor: parseString(res.cursor), } } const hydration = async ( - state: SkeletonState, - ctx: Context, -): Promise => { - const { feedService } = ctx - const { params, feedItems } = state - const refs = feedService.feedItemRefs(feedItems) - const hydrated = await feedService.feedHydration({ - ...refs, - viewer: params.viewer, - }) - return { ...state, ...hydrated } + inputs: HydrationFnInput, +) => { + const { ctx, params, skeleton } = inputs + return ctx.hydrator.hydratePosts( + skeleton.posts.map((uri) => ({ uri })), + params.viewer, + ) } -const noBlocks = (state: HydrationState): HydrationState => { - const { viewer } = state.params - state.feedItems = state.feedItems.filter((item) => { - if (!viewer) return true - return !state.bam.block([viewer, item.postAuthorDid]) +const noBlocks = (inputs: RulesFnInput) => { + const { ctx, skeleton, hydration } = inputs + skeleton.posts = skeleton.posts.filter((uri) => { + const creator = creatorFromUri(uri) + return !ctx.views.viewerBlockExists(creator, hydration) }) - return state + return skeleton } -const presentation = (state: HydrationState, ctx: Context) => { - const { feedService, actorService } = ctx - const { feedItems, profiles, params } = state - const actors = actorService.views.profileBasicPresentation( - Object.keys(profiles), - state, - params.viewer, - ) - - const postViews = mapDefined(feedItems, (item) => - feedService.views.formatPostView( - item.postUri, - actors, - state.posts, - state.threadgates, - state.embeds, - state.labels, - state.lists, - params.viewer, - ), +const presentation = ( + inputs: PresentationFnInput, +) => { + const { ctx, skeleton, hydration } = inputs + const posts = mapDefined(skeleton.posts, (uri) => + ctx.views.post(uri, hydration), ) - return { posts: postViews, cursor: state.cursor, hitsTotal: state.hitsTotal } + return { + posts, + cursor: skeleton.cursor, + hitsTotal: skeleton.hitsTotal, + } } type Context = { - db: Database - feedService: FeedService - actorService: ActorService - searchAgent: AtpAgent + dataplane: DataPlaneClient + hydrator: Hydrator + views: Views + searchAgent?: AtpAgent } type Params = QueryParams & { viewer: string | null } -type SkeletonState = { - params: Params - feedItems: FeedRow[] +type Skeleton = { + posts: string[] hitsTotal?: number cursor?: string } - -type HydrationState = SkeletonState & FeedHydrationState diff --git a/packages/bsky/src/api/app/bsky/graph/getBlocks.ts b/packages/bsky/src/api/app/bsky/graph/getBlocks.ts index adc28752a25..1df10bf5a23 100644 --- a/packages/bsky/src/api/app/bsky/graph/getBlocks.ts +++ b/packages/bsky/src/api/app/bsky/graph/getBlocks.ts @@ -1,54 +1,84 @@ +import { mapDefined } from '@atproto/common' import { Server } from '../../../../lexicon' -import { paginate, TimeCidKeyset } from '../../../../db/pagination' +import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/getBlocks' import AppContext from '../../../../context' -import { notSoftDeletedClause } from '../../../../db/util' +import { + createPipeline, + HydrationFnInput, + noRules, + PresentationFnInput, + SkeletonFnInput, +} from '../../../../pipeline' +import { Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { clearlyBadCursor } from '../../../util' export default function (server: Server, ctx: AppContext) { + const getBlocks = createPipeline(skeleton, hydration, noRules, presentation) server.app.bsky.graph.getBlocks({ auth: ctx.authVerifier.standard, handler: async ({ params, auth }) => { - const { limit, cursor } = params - const requester = auth.credentials.iss - if (TimeCidKeyset.clearlyBad(cursor)) { - return { - encoding: 'application/json', - body: { blocks: [] }, - } + const viewer = auth.credentials.iss + const result = await getBlocks({ ...params, viewer }, ctx) + return { + encoding: 'application/json', + body: result, } + }, + }) +} - const db = ctx.db.getReplica() - const { ref } = db.db.dynamic +const skeleton = async (input: SkeletonFnInput) => { + const { params, ctx } = input + if (clearlyBadCursor(params.cursor)) { + return { blockedDids: [] } + } + const { blockUris, cursor } = await ctx.hydrator.dataplane.getBlocks({ + actorDid: params.viewer, + cursor: params.cursor, + limit: params.limit, + }) + const blocks = await ctx.hydrator.graph.getBlocks(blockUris) + const blockedDids = mapDefined( + blockUris, + (uri) => blocks.get(uri)?.record.subject, + ) + return { + blockedDids, + cursor: cursor || undefined, + } +} - let blocksReq = db.db - .selectFrom('actor_block') - .where('actor_block.creator', '=', requester) - .innerJoin('actor as subject', 'subject.did', 'actor_block.subjectDid') - .where(notSoftDeletedClause(ref('subject'))) - .selectAll('subject') - .select(['actor_block.cid as cid', 'actor_block.sortAt as sortAt']) +const hydration = async ( + input: HydrationFnInput, +) => { + const { ctx, params, skeleton } = input + const { viewer } = params + const { blockedDids } = skeleton + return ctx.hydrator.hydrateProfiles(blockedDids, viewer) +} - const keyset = new TimeCidKeyset( - ref('actor_block.sortAt'), - ref('actor_block.cid'), - ) - blocksReq = paginate(blocksReq, { - limit, - cursor, - keyset, - }) +const presentation = ( + input: PresentationFnInput, +) => { + const { ctx, hydration, skeleton } = input + const { blockedDids, cursor } = skeleton + const blocks = mapDefined(blockedDids, (did) => { + return ctx.views.profile(did, hydration) + }) + return { blocks, cursor } +} - const blocksRes = await blocksReq.execute() +type Context = { + hydrator: Hydrator + views: Views +} - const actorService = ctx.services.actor(db) - const blocks = await actorService.views.profilesList(blocksRes, requester) +type Params = QueryParams & { + viewer: string +} - return { - encoding: 'application/json', - body: { - blocks, - cursor: keyset.packFromResult(blocksRes), - }, - } - }, - }) +type SkeletonState = { + blockedDids: string[] + cursor?: string } diff --git a/packages/bsky/src/api/app/bsky/graph/getFollowers.ts b/packages/bsky/src/api/app/bsky/graph/getFollowers.ts index bf22b2be6cb..c771d034cfd 100644 --- a/packages/bsky/src/api/app/bsky/graph/getFollowers.ts +++ b/packages/bsky/src/api/app/bsky/graph/getFollowers.ts @@ -3,32 +3,33 @@ import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/getFollowers' import AppContext from '../../../../context' -import { Database } from '../../../../db' -import { notSoftDeletedClause } from '../../../../db/util' -import { paginate, TimeCidKeyset } from '../../../../db/pagination' -import { Actor } from '../../../../db/tables/actor' -import { ActorInfoMap, ActorService } from '../../../../services/actor' -import { BlockAndMuteState, GraphService } from '../../../../services/graph' -import { createPipeline } from '../../../../pipeline' +import { + HydrationFnInput, + PresentationFnInput, + RulesFnInput, + SkeletonFnInput, + createPipeline, +} from '../../../../pipeline' +import { didFromUri } from '../../../../hydration/util' +import { Hydrator, mergeStates } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { clearlyBadCursor } from '../../../util' export default function (server: Server, ctx: AppContext) { const getFollowers = createPipeline( skeleton, hydration, - noBlocksInclInvalid, + noBlocks, presentation, ) server.app.bsky.graph.getFollowers({ auth: ctx.authVerifier.optionalStandardOrRole, handler: async ({ params, auth }) => { - const db = ctx.db.getReplica() - const actorService = ctx.services.actor(db) - const graphService = ctx.services.graph(db) const { viewer, canViewTakedowns } = ctx.authVerifier.parseCreds(auth) const result = await getFollowers( { ...params, viewer, canViewTakedowns }, - { db, actorService, graphService }, + ctx, ) return { @@ -39,95 +40,86 @@ export default function (server: Server, ctx: AppContext) { }) } -const skeleton = async ( - params: Params, - ctx: Context, -): Promise => { - const { db, actorService } = ctx - const { limit, cursor, actor, canViewTakedowns } = params - const { ref } = db.db.dynamic - - const subject = await actorService.getActor(actor, canViewTakedowns) - if (!subject) { - throw new InvalidRequestError(`Actor not found: ${actor}`) +const skeleton = async (input: SkeletonFnInput) => { + const { params, ctx } = input + const [subjectDid] = await ctx.hydrator.actor.getDidsDefined([params.actor]) + if (!subjectDid) { + throw new InvalidRequestError(`Actor not found: ${params.actor}`) } - - if (TimeCidKeyset.clearlyBad(cursor)) { - return { params, followers: [], subject } + if (clearlyBadCursor(params.cursor)) { + return { subjectDid, followUris: [] } } - - let followersReq = db.db - .selectFrom('follow') - .where('follow.subjectDid', '=', subject.did) - .innerJoin('actor as creator', 'creator.did', 'follow.creator') - .if(!canViewTakedowns, (qb) => - qb.where(notSoftDeletedClause(ref('creator'))), - ) - .selectAll('creator') - .select(['follow.cid as cid', 'follow.sortAt as sortAt']) - - const keyset = new TimeCidKeyset(ref('follow.sortAt'), ref('follow.cid')) - followersReq = paginate(followersReq, { - limit, - cursor, - keyset, + const { followers, cursor } = await ctx.hydrator.graph.getActorFollowers({ + did: subjectDid, + cursor: params.cursor, + limit: params.limit, }) - - const followers = await followersReq.execute() return { - params, - followers, - subject, - cursor: keyset.packFromResult(followers), + subjectDid, + followUris: followers.map((f) => f.uri), + cursor: cursor || undefined, } } -const hydration = async (state: SkeletonState, ctx: Context) => { - const { graphService, actorService } = ctx - const { params, followers, subject } = state +const hydration = async ( + input: HydrationFnInput, +) => { + const { ctx, params, skeleton } = input const { viewer } = params - const [actors, bam] = await Promise.all([ - actorService.views.profiles([subject, ...followers], viewer), - graphService.getBlockAndMuteState( - followers.flatMap((item) => { - if (viewer) { - return [ - [viewer, item.did], - [subject.did, item.did], - ] - } - return [[subject.did, item.did]] - }), - ), - ]) - return { ...state, bam, actors } + const { followUris, subjectDid } = skeleton + const followState = await ctx.hydrator.hydrateFollows(followUris) + const dids = [subjectDid] + if (followState.follows) { + for (const [uri, follow] of followState.follows) { + if (follow) { + dids.push(didFromUri(uri)) + } + } + } + const profileState = await ctx.hydrator.hydrateProfiles(dids, viewer) + return mergeStates(followState, profileState) } -const noBlocksInclInvalid = (state: HydrationState) => { - const { subject } = state - const { viewer } = state.params - state.followers = state.followers.filter( - (item) => - !state.bam.block([subject.did, item.did]) && - (!viewer || !state.bam.block([viewer, item.did])), - ) - return state +const noBlocks = (input: RulesFnInput) => { + const { skeleton, params, hydration, ctx } = input + const { viewer } = params + skeleton.followUris = skeleton.followUris.filter((followUri) => { + const followerDid = didFromUri(followUri) + return ( + !hydration.followBlocks?.get(followUri) && + (!viewer || !ctx.views.viewerBlockExists(followerDid, hydration)) + ) + }) + return skeleton } -const presentation = (state: HydrationState) => { - const { params, followers, subject, actors, cursor } = state - const subjectView = actors[subject.did] - const followersView = mapDefined(followers, (item) => actors[item.did]) - if (!subjectView) { +const presentation = ( + input: PresentationFnInput, +) => { + const { ctx, hydration, skeleton, params } = input + const { subjectDid, followUris, cursor } = skeleton + const isTakendown = (did: string) => + ctx.views.actorIsTakendown(did, hydration) + + const subject = ctx.views.profile(subjectDid, hydration) + if (!subject || (!params.canViewTakedowns && isTakendown(subjectDid))) { throw new InvalidRequestError(`Actor not found: ${params.actor}`) } - return { followers: followersView, subject: subjectView, cursor } + + const followers = mapDefined(followUris, (followUri) => { + const followerDid = didFromUri(followUri) + if (!params.canViewTakedowns && isTakendown(followerDid)) { + return + } + return ctx.views.profile(didFromUri(followUri), hydration) + }) + + return { followers, subject, cursor } } type Context = { - db: Database - actorService: ActorService - graphService: GraphService + hydrator: Hydrator + views: Views } type Params = QueryParams & { @@ -136,13 +128,7 @@ type Params = QueryParams & { } type SkeletonState = { - params: Params - followers: Actor[] - subject: Actor + subjectDid: string + followUris: string[] cursor?: string } - -type HydrationState = SkeletonState & { - bam: BlockAndMuteState - actors: ActorInfoMap -} diff --git a/packages/bsky/src/api/app/bsky/graph/getFollows.ts b/packages/bsky/src/api/app/bsky/graph/getFollows.ts index 8d294b27354..81df38e453e 100644 --- a/packages/bsky/src/api/app/bsky/graph/getFollows.ts +++ b/packages/bsky/src/api/app/bsky/graph/getFollows.ts @@ -1,34 +1,30 @@ import { mapDefined } from '@atproto/common' import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' -import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/getFollows' +import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/getFollowers' import AppContext from '../../../../context' -import { Database } from '../../../../db' -import { notSoftDeletedClause } from '../../../../db/util' -import { paginate, TimeCidKeyset } from '../../../../db/pagination' -import { Actor } from '../../../../db/tables/actor' -import { ActorInfoMap, ActorService } from '../../../../services/actor' -import { BlockAndMuteState, GraphService } from '../../../../services/graph' -import { createPipeline } from '../../../../pipeline' +import { + HydrationFnInput, + PresentationFnInput, + RulesFnInput, + SkeletonFnInput, + createPipeline, +} from '../../../../pipeline' +import { Hydrator, mergeStates } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { clearlyBadCursor } from '../../../util' export default function (server: Server, ctx: AppContext) { - const getFollows = createPipeline( - skeleton, - hydration, - noBlocksInclInvalid, - presentation, - ) + const getFollows = createPipeline(skeleton, hydration, noBlocks, presentation) server.app.bsky.graph.getFollows({ auth: ctx.authVerifier.optionalStandardOrRole, handler: async ({ params, auth }) => { - const db = ctx.db.getReplica() - const actorService = ctx.services.actor(db) - const graphService = ctx.services.graph(db) const { viewer, canViewTakedowns } = ctx.authVerifier.parseCreds(auth) + // @TODO ensure canViewTakedowns gets threaded through and applied properly const result = await getFollows( { ...params, viewer, canViewTakedowns }, - { db, actorService, graphService }, + ctx, ) return { @@ -39,96 +35,89 @@ export default function (server: Server, ctx: AppContext) { }) } -const skeleton = async ( - params: Params, - ctx: Context, -): Promise => { - const { db, actorService } = ctx - const { limit, cursor, actor, canViewTakedowns } = params - const { ref } = db.db.dynamic - - const creator = await actorService.getActor(actor, canViewTakedowns) - if (!creator) { - throw new InvalidRequestError(`Actor not found: ${actor}`) +const skeleton = async (input: SkeletonFnInput) => { + const { params, ctx } = input + const [subjectDid] = await ctx.hydrator.actor.getDidsDefined([params.actor]) + if (!subjectDid) { + throw new InvalidRequestError(`Actor not found: ${params.actor}`) } - - if (TimeCidKeyset.clearlyBad(cursor)) { - return { params, follows: [], creator } + if (clearlyBadCursor(params.cursor)) { + return { subjectDid, followUris: [] } } - - let followsReq = db.db - .selectFrom('follow') - .where('follow.creator', '=', creator.did) - .innerJoin('actor as subject', 'subject.did', 'follow.subjectDid') - .if(!canViewTakedowns, (qb) => - qb.where(notSoftDeletedClause(ref('subject'))), - ) - .selectAll('subject') - .select(['follow.cid as cid', 'follow.sortAt as sortAt']) - - const keyset = new TimeCidKeyset(ref('follow.sortAt'), ref('follow.cid')) - followsReq = paginate(followsReq, { - limit, - cursor, - keyset, + const { follows, cursor } = await ctx.hydrator.graph.getActorFollows({ + did: subjectDid, + cursor: params.cursor, + limit: params.limit, }) - - const follows = await followsReq.execute() - return { - params, - follows, - creator, - cursor: keyset.packFromResult(follows), + subjectDid, + followUris: follows.map((f) => f.uri), + cursor: cursor || undefined, } } -const hydration = async (state: SkeletonState, ctx: Context) => { - const { graphService, actorService } = ctx - const { params, follows, creator } = state +const hydration = async ( + input: HydrationFnInput, +) => { + const { ctx, params, skeleton } = input const { viewer } = params - const [actors, bam] = await Promise.all([ - actorService.views.profiles([creator, ...follows], viewer), - graphService.getBlockAndMuteState( - follows.flatMap((item) => { - if (viewer) { - return [ - [viewer, item.did], - [creator.did, item.did], - ] - } - return [[creator.did, item.did]] - }), - ), - ]) - return { ...state, bam, actors } + const { followUris, subjectDid } = skeleton + const followState = await ctx.hydrator.hydrateFollows(followUris) + const dids = [subjectDid] + if (followState.follows) { + for (const follow of followState.follows.values()) { + if (follow) { + dids.push(follow.record.subject) + } + } + } + const profileState = await ctx.hydrator.hydrateProfiles(dids, viewer) + return mergeStates(followState, profileState) } -const noBlocksInclInvalid = (state: HydrationState) => { - const { creator } = state - const { viewer } = state.params - state.follows = state.follows.filter( - (item) => - !state.bam.block([creator.did, item.did]) && - (!viewer || !state.bam.block([viewer, item.did])), - ) - return state +const noBlocks = (input: RulesFnInput) => { + const { skeleton, params, hydration, ctx } = input + const { viewer } = params + skeleton.followUris = skeleton.followUris.filter((followUri) => { + const follow = hydration.follows?.get(followUri) + if (!follow) return false + return ( + !hydration.followBlocks?.get(followUri) && + (!viewer || + !ctx.views.viewerBlockExists(follow.record.subject, hydration)) + ) + }) + return skeleton } -const presentation = (state: HydrationState) => { - const { params, follows, creator, actors, cursor } = state - const creatorView = actors[creator.did] - const followsView = mapDefined(follows, (item) => actors[item.did]) - if (!creatorView) { +const presentation = ( + input: PresentationFnInput, +) => { + const { ctx, hydration, skeleton, params } = input + const { subjectDid, followUris, cursor } = skeleton + const isTakendown = (did: string) => + ctx.views.actorIsTakendown(did, hydration) + + const subject = ctx.views.profile(subjectDid, hydration) + if (!subject || (!params.canViewTakedowns && isTakendown(subjectDid))) { throw new InvalidRequestError(`Actor not found: ${params.actor}`) } - return { follows: followsView, subject: creatorView, cursor } + + const follows = mapDefined(followUris, (followUri) => { + const followDid = hydration.follows?.get(followUri)?.record.subject + if (!followDid) return + if (!params.canViewTakedowns && isTakendown(followDid)) { + return + } + return ctx.views.profile(followDid, hydration) + }) + + return { follows, subject, cursor } } type Context = { - db: Database - actorService: ActorService - graphService: GraphService + hydrator: Hydrator + views: Views } type Params = QueryParams & { @@ -137,13 +126,7 @@ type Params = QueryParams & { } type SkeletonState = { - params: Params - follows: Actor[] - creator: Actor + subjectDid: string + followUris: string[] cursor?: string } - -type HydrationState = SkeletonState & { - bam: BlockAndMuteState - actors: ActorInfoMap -} diff --git a/packages/bsky/src/api/app/bsky/graph/getList.ts b/packages/bsky/src/api/app/bsky/graph/getList.ts index 82007b45388..e8204d4ac0d 100644 --- a/packages/bsky/src/api/app/bsky/graph/getList.ts +++ b/packages/bsky/src/api/app/bsky/graph/getList.ts @@ -3,28 +3,25 @@ import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/getList' import AppContext from '../../../../context' -import { Database } from '../../../../db' -import { paginate, TimeCidKeyset } from '../../../../db/pagination' -import { Actor } from '../../../../db/tables/actor' -import { GraphService, ListInfo } from '../../../../services/graph' -import { ActorService, ProfileHydrationState } from '../../../../services/actor' -import { createPipeline, noRules } from '../../../../pipeline' +import { + createPipeline, + HydrationFnInput, + noRules, + PresentationFnInput, + SkeletonFnInput, +} from '../../../../pipeline' +import { Hydrator, mergeStates } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { clearlyBadCursor } from '../../../util' +import { ListItemInfo } from '../../../../proto/bsky_pb' export default function (server: Server, ctx: AppContext) { const getList = createPipeline(skeleton, hydration, noRules, presentation) server.app.bsky.graph.getList({ auth: ctx.authVerifier.standardOptional, handler: async ({ params, auth }) => { - const db = ctx.db.getReplica() - const graphService = ctx.services.graph(db) - const actorService = ctx.services.actor(db) const viewer = auth.credentials.iss - - const result = await getList( - { ...params, viewer }, - { db, graphService, actorService }, - ) - + const result = await getList({ ...params, viewer }, ctx) return { encoding: 'application/json', body: result, @@ -34,89 +31,60 @@ export default function (server: Server, ctx: AppContext) { } const skeleton = async ( - params: Params, - ctx: Context, + input: SkeletonFnInput, ): Promise => { - const { db, graphService } = ctx - const { list, limit, cursor, viewer } = params - const { ref } = db.db.dynamic - - const listRes = await graphService - .getListsQb(viewer) - .where('list.uri', '=', list) - .executeTakeFirst() - if (!listRes) { - throw new InvalidRequestError(`List not found: ${list}`) - } - - if (TimeCidKeyset.clearlyBad(cursor)) { - return { params, list: listRes, listItems: [] } + const { ctx, params } = input + if (clearlyBadCursor(params.cursor)) { + return { listUri: params.list, listitems: [] } } - - let itemsReq = graphService - .getListItemsQb() - .where('list_item.listUri', '=', list) - .where('list_item.creator', '=', listRes.creator) - - const keyset = new TimeCidKeyset( - ref('list_item.sortAt'), - ref('list_item.cid'), - ) - - itemsReq = paginate(itemsReq, { - limit, - cursor, - keyset, + const { listitems, cursor } = await ctx.hydrator.dataplane.getListMembers({ + listUri: params.list, + limit: params.limit, + cursor: params.cursor, }) - - const listItems = await itemsReq.execute() - return { - params, - list: listRes, - listItems, - cursor: keyset.packFromResult(listItems), + listUri: params.list, + listitems, + cursor: cursor || undefined, } } -const hydration = async (state: SkeletonState, ctx: Context) => { - const { actorService } = ctx - const { params, list, listItems } = state - const profileState = await actorService.views.profileHydration( - [list, ...listItems].map((x) => x.did), - { viewer: params.viewer }, - ) - return { ...state, ...profileState } +const hydration = async ( + input: HydrationFnInput, +) => { + const { ctx, params, skeleton } = input + const { viewer } = params + const { listUri, listitems } = skeleton + const [listState, profileState] = await Promise.all([ + ctx.hydrator.hydrateLists([listUri], viewer), + ctx.hydrator.hydrateProfiles( + listitems.map(({ did }) => did), + viewer, + ), + ]) + return mergeStates(listState, profileState) } -const presentation = (state: HydrationState, ctx: Context) => { - const { actorService, graphService } = ctx - const { params, list, listItems, cursor, ...profileState } = state - const actors = actorService.views.profilePresentation( - Object.keys(profileState.profiles), - profileState, - params.viewer, - ) - const creator = actors[list.creator] - if (!creator) { - throw new InvalidRequestError(`Actor not found: ${list.handle}`) - } - const listView = graphService.formatListView(list, actors) - if (!listView) { - throw new InvalidRequestError('List not found') - } - const items = mapDefined(listItems, (item) => { - const subject = actors[item.did] +const presentation = ( + input: PresentationFnInput, +) => { + const { ctx, skeleton, hydration } = input + const { listUri, listitems, cursor } = skeleton + const list = ctx.views.list(listUri, hydration) + const items = mapDefined(listitems, ({ uri, did }) => { + const subject = ctx.views.profile(did, hydration) if (!subject) return - return { uri: item.uri, subject } + return { uri, subject } }) - return { list: listView, items, cursor } + if (!list) { + throw new InvalidRequestError('List not found') + } + return { list, items, cursor } } type Context = { - db: Database - actorService: ActorService - graphService: GraphService + hydrator: Hydrator + views: Views } type Params = QueryParams & { @@ -124,10 +92,7 @@ type Params = QueryParams & { } type SkeletonState = { - params: Params - list: Actor & ListInfo - listItems: (Actor & { uri: string; cid: string; sortAt: string })[] + listUri: string + listitems: ListItemInfo[] cursor?: string } - -type HydrationState = SkeletonState & ProfileHydrationState diff --git a/packages/bsky/src/api/app/bsky/graph/getListBlocks.ts b/packages/bsky/src/api/app/bsky/graph/getListBlocks.ts index 6fb6df55e82..ae4a9a27b59 100644 --- a/packages/bsky/src/api/app/bsky/graph/getListBlocks.ts +++ b/packages/bsky/src/api/app/bsky/graph/getListBlocks.ts @@ -1,13 +1,17 @@ import { mapDefined } from '@atproto/common' import { Server } from '../../../../lexicon' import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/getListBlocks' -import { paginate, TimeCidKeyset } from '../../../../db/pagination' import AppContext from '../../../../context' -import { Database } from '../../../../db' -import { Actor } from '../../../../db/tables/actor' -import { GraphService, ListInfo } from '../../../../services/graph' -import { ActorService, ProfileHydrationState } from '../../../../services/actor' -import { createPipeline, noRules } from '../../../../pipeline' +import { + createPipeline, + HydrationFnInput, + noRules, + PresentationFnInput, + SkeletonFnInput, +} from '../../../../pipeline' +import { Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { clearlyBadCursor } from '../../../util' export default function (server: Server, ctx: AppContext) { const getListBlocks = createPipeline( @@ -19,16 +23,8 @@ export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.getListBlocks({ auth: ctx.authVerifier.standard, handler: async ({ params, auth }) => { - const db = ctx.db.getReplica() - const graphService = ctx.services.graph(db) - const actorService = ctx.services.actor(db) const viewer = auth.credentials.iss - - const result = await getListBlocks( - { ...params, viewer }, - { db, actorService, graphService }, - ) - + const result = await getListBlocks({ ...params, viewer }, ctx) return { encoding: 'application/json', body: result, @@ -38,72 +34,40 @@ export default function (server: Server, ctx: AppContext) { } const skeleton = async ( - params: Params, - ctx: Context, + input: SkeletonFnInput, ): Promise => { - const { db, graphService } = ctx - const { limit, cursor, viewer } = params - const { ref } = db.db.dynamic - - if (TimeCidKeyset.clearlyBad(cursor)) { - return { params, listInfos: [] } - } - - let listsReq = graphService - .getListsQb(viewer) - .whereExists( - db.db - .selectFrom('list_block') - .where('list_block.creator', '=', viewer) - .whereRef('list_block.subjectUri', '=', ref('list.uri')) - .selectAll(), - ) - - const keyset = new TimeCidKeyset(ref('list.createdAt'), ref('list.cid')) - - listsReq = paginate(listsReq, { - limit, - cursor, - keyset, - }) - - const listInfos = await listsReq.execute() - - return { - params, - listInfos, - cursor: keyset.packFromResult(listInfos), + const { ctx, params } = input + if (clearlyBadCursor(params.cursor)) { + return { listUris: [] } } + const { listUris, cursor } = + await ctx.hydrator.dataplane.getBlocklistSubscriptions({ + actorDid: params.viewer, + cursor: params.cursor, + limit: params.limit, + }) + return { listUris, cursor: cursor || undefined } } -const hydration = async (state: SkeletonState, ctx: Context) => { - const { actorService } = ctx - const { params, listInfos } = state - const profileState = await actorService.views.profileHydration( - listInfos.map((list) => list.creator), - { viewer: params.viewer }, - ) - return { ...state, ...profileState } +const hydration = async ( + input: HydrationFnInput, +) => { + const { ctx, params, skeleton } = input + return await ctx.hydrator.hydrateLists(skeleton.listUris, params.viewer) } -const presentation = (state: HydrationState, ctx: Context) => { - const { actorService, graphService } = ctx - const { params, listInfos, cursor, ...profileState } = state - const actors = actorService.views.profilePresentation( - Object.keys(profileState.profiles), - profileState, - params.viewer, - ) - const lists = mapDefined(listInfos, (list) => - graphService.formatListView(list, actors), - ) +const presentation = ( + input: PresentationFnInput, +) => { + const { ctx, skeleton, hydration } = input + const { listUris, cursor } = skeleton + const lists = mapDefined(listUris, (uri) => ctx.views.list(uri, hydration)) return { lists, cursor } } type Context = { - db: Database - actorService: ActorService - graphService: GraphService + hydrator: Hydrator + views: Views } type Params = QueryParams & { @@ -111,9 +75,6 @@ type Params = QueryParams & { } type SkeletonState = { - params: Params - listInfos: (Actor & ListInfo)[] + listUris: string[] cursor?: string } - -type HydrationState = SkeletonState & ProfileHydrationState diff --git a/packages/bsky/src/api/app/bsky/graph/getListMutes.ts b/packages/bsky/src/api/app/bsky/graph/getListMutes.ts index 8baa509ae47..20060a5b9a2 100644 --- a/packages/bsky/src/api/app/bsky/graph/getListMutes.ts +++ b/packages/bsky/src/api/app/bsky/graph/getListMutes.ts @@ -1,58 +1,80 @@ import { mapDefined } from '@atproto/common' import { Server } from '../../../../lexicon' -import { paginate, TimeCidKeyset } from '../../../../db/pagination' +import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/getListBlocks' import AppContext from '../../../../context' +import { + createPipeline, + HydrationFnInput, + noRules, + PresentationFnInput, + SkeletonFnInput, +} from '../../../../pipeline' +import { Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { clearlyBadCursor } from '../../../util' export default function (server: Server, ctx: AppContext) { + const getListMutes = createPipeline( + skeleton, + hydration, + noRules, + presentation, + ) server.app.bsky.graph.getListMutes({ auth: ctx.authVerifier.standard, handler: async ({ params, auth }) => { - const { limit, cursor } = params - const requester = auth.credentials.iss - if (TimeCidKeyset.clearlyBad(cursor)) { - return { - encoding: 'application/json', - body: { lists: [] }, - } + const viewer = auth.credentials.iss + const result = await getListMutes({ ...params, viewer }, ctx) + return { + encoding: 'application/json', + body: result, } + }, + }) +} - const db = ctx.db.getReplica() - const { ref } = db.db.dynamic - - const graphService = ctx.services.graph(db) +const skeleton = async ( + input: SkeletonFnInput, +): Promise => { + const { ctx, params } = input + if (clearlyBadCursor(params.cursor)) { + return { listUris: [] } + } + const { listUris, cursor } = + await ctx.hydrator.dataplane.getMutelistSubscriptions({ + actorDid: params.viewer, + cursor: params.cursor, + limit: params.limit, + }) + return { listUris, cursor: cursor || undefined } +} - let listsReq = graphService - .getListsQb(requester) - .whereExists( - db.db - .selectFrom('list_mute') - .where('list_mute.mutedByDid', '=', requester) - .whereRef('list_mute.listUri', '=', ref('list.uri')) - .selectAll(), - ) +const hydration = async ( + input: HydrationFnInput, +) => { + const { ctx, params, skeleton } = input + return await ctx.hydrator.hydrateLists(skeleton.listUris, params.viewer) +} - const keyset = new TimeCidKeyset(ref('list.createdAt'), ref('list.cid')) - listsReq = paginate(listsReq, { - limit, - cursor, - keyset, - }) - const listsRes = await listsReq.execute() +const presentation = ( + input: PresentationFnInput, +) => { + const { ctx, skeleton, hydration } = input + const { listUris, cursor } = skeleton + const lists = mapDefined(listUris, (uri) => ctx.views.list(uri, hydration)) + return { lists, cursor } +} - const actorService = ctx.services.actor(db) - const profiles = await actorService.views.profiles(listsRes, requester) +type Context = { + hydrator: Hydrator + views: Views +} - const lists = mapDefined(listsRes, (row) => - graphService.formatListView(row, profiles), - ) +type Params = QueryParams & { + viewer: string +} - return { - encoding: 'application/json', - body: { - lists, - cursor: keyset.packFromResult(listsRes), - }, - } - }, - }) +type SkeletonState = { + listUris: string[] + cursor?: string } diff --git a/packages/bsky/src/api/app/bsky/graph/getLists.ts b/packages/bsky/src/api/app/bsky/graph/getLists.ts index 40bb903f5b4..0ff90f5c4bf 100644 --- a/packages/bsky/src/api/app/bsky/graph/getLists.ts +++ b/packages/bsky/src/api/app/bsky/graph/getLists.ts @@ -1,63 +1,79 @@ import { mapDefined } from '@atproto/common' -import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' -import { paginate, TimeCidKeyset } from '../../../../db/pagination' +import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/getLists' import AppContext from '../../../../context' +import { + createPipeline, + HydrationFnInput, + noRules, + PresentationFnInput, + SkeletonFnInput, +} from '../../../../pipeline' +import { Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { clearlyBadCursor } 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, handler: async ({ params, auth }) => { - const { actor, limit, cursor } = params - const requester = auth.credentials.iss - if (TimeCidKeyset.clearlyBad(cursor)) { - return { - encoding: 'application/json', - body: { lists: [] }, - } + const viewer = auth.credentials.iss + const result = await getLists({ ...params, viewer }, ctx) + + return { + encoding: 'application/json', + body: result, } + }, + }) +} - const db = ctx.db.getReplica() - const { ref } = db.db.dynamic +const skeleton = async ( + input: SkeletonFnInput, +): Promise => { + const { ctx, params } = input + if (clearlyBadCursor(params.cursor)) { + return { listUris: [] } + } + const { listUris, cursor } = await ctx.hydrator.dataplane.getActorLists({ + actorDid: params.actor, + cursor: params.cursor, + limit: params.limit, + }) + return { listUris, cursor: cursor || undefined } +} - const actorService = ctx.services.actor(db) - const graphService = ctx.services.graph(db) +const hydration = async ( + input: HydrationFnInput, +) => { + const { ctx, params, skeleton } = input + const { viewer } = params + const { listUris } = skeleton + return ctx.hydrator.hydrateLists(listUris, viewer) +} - const creatorRes = await actorService.getActor(actor) - if (!creatorRes) { - throw new InvalidRequestError(`Actor not found: ${actor}`) - } +const presentation = ( + input: PresentationFnInput, +) => { + const { ctx, skeleton, hydration } = input + const { listUris, cursor } = skeleton + const lists = mapDefined(listUris, (uri) => { + return ctx.views.list(uri, hydration) + }) + return { lists, cursor } +} - let listsReq = graphService - .getListsQb(requester) - .where('list.creator', '=', creatorRes.did) - - const keyset = new TimeCidKeyset(ref('list.sortAt'), ref('list.cid')) - listsReq = paginate(listsReq, { - limit, - cursor, - keyset, - }) - - const [listsRes, profiles] = await Promise.all([ - listsReq.execute(), - actorService.views.profiles([creatorRes], requester), - ]) - if (!profiles[creatorRes.did]) { - throw new InvalidRequestError(`Actor not found: ${actor}`) - } +type Context = { + hydrator: Hydrator + views: Views +} - const lists = mapDefined(listsRes, (row) => - graphService.formatListView(row, profiles), - ) +type Params = QueryParams & { + viewer: string | null +} - return { - encoding: 'application/json', - body: { - lists, - cursor: keyset.packFromResult(listsRes), - }, - } - }, - }) +type SkeletonState = { + listUris: string[] + cursor?: string } diff --git a/packages/bsky/src/api/app/bsky/graph/getMutes.ts b/packages/bsky/src/api/app/bsky/graph/getMutes.ts index 827573258bd..3abd417eb87 100644 --- a/packages/bsky/src/api/app/bsky/graph/getMutes.ts +++ b/packages/bsky/src/api/app/bsky/graph/getMutes.ts @@ -1,62 +1,79 @@ +import { mapDefined } from '@atproto/common' import { Server } from '../../../../lexicon' -import { paginate, TimeCidKeyset } from '../../../../db/pagination' +import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/getMutes' import AppContext from '../../../../context' -import { notSoftDeletedClause } from '../../../../db/util' +import { Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { + HydrationFnInput, + PresentationFnInput, + SkeletonFnInput, + createPipeline, + noRules, +} from '../../../../pipeline' +import { clearlyBadCursor } from '../../../util' export default function (server: Server, ctx: AppContext) { + const getMutes = createPipeline(skeleton, hydration, noRules, presentation) server.app.bsky.graph.getMutes({ auth: ctx.authVerifier.standard, handler: async ({ params, auth }) => { - const { limit, cursor } = params - const requester = auth.credentials.iss - if (TimeCidKeyset.clearlyBad(cursor)) { - return { - encoding: 'application/json', - body: { mutes: [] }, - } - } - - const db = ctx.db.getReplica() - const { ref } = db.db.dynamic - - let mutesReq = db.db - .selectFrom('mute') - .innerJoin('actor', 'actor.did', 'mute.subjectDid') - .where(notSoftDeletedClause(ref('actor'))) - .where('mute.mutedByDid', '=', requester) - .selectAll('actor') - .select('mute.createdAt as createdAt') - - const keyset = new CreatedAtDidKeyset( - ref('mute.createdAt'), - ref('mute.subjectDid'), - ) - mutesReq = paginate(mutesReq, { - limit, - cursor, - keyset, - }) - - const mutesRes = await mutesReq.execute() - - const actorService = ctx.services.actor(db) - + const viewer = auth.credentials.iss + const result = await getMutes({ ...params, viewer }, ctx) return { encoding: 'application/json', - body: { - cursor: keyset.packFromResult(mutesRes), - mutes: await actorService.views.profilesList(mutesRes, requester), - }, + body: result, } }, }) } -export class CreatedAtDidKeyset extends TimeCidKeyset<{ - createdAt: string - did: string // dids are treated identically to cids in TimeCidKeyset -}> { - labelResult(result: { createdAt: string; did: string }) { - return { primary: result.createdAt, secondary: result.did } +const skeleton = async (input: SkeletonFnInput) => { + const { params, ctx } = input + if (clearlyBadCursor(params.cursor)) { + return { mutedDids: [] } } + const { dids, cursor } = await ctx.hydrator.dataplane.getMutes({ + actorDid: params.viewer, + cursor: params.cursor, + limit: params.limit, + }) + return { + mutedDids: dids, + cursor: cursor || undefined, + } +} + +const hydration = async ( + input: HydrationFnInput, +) => { + const { ctx, params, skeleton } = input + const { viewer } = params + const { mutedDids } = skeleton + return ctx.hydrator.hydrateProfiles(mutedDids, viewer) +} + +const presentation = ( + input: PresentationFnInput, +) => { + const { ctx, hydration, skeleton } = input + const { mutedDids, cursor } = skeleton + const mutes = mapDefined(mutedDids, (did) => { + return ctx.views.profile(did, hydration) + }) + return { mutes, cursor } +} + +type Context = { + hydrator: Hydrator + views: Views +} + +type Params = QueryParams & { + viewer: string +} + +type SkeletonState = { + mutedDids: string[] + cursor?: string } diff --git a/packages/bsky/src/api/app/bsky/graph/getRelationships.ts b/packages/bsky/src/api/app/bsky/graph/getRelationships.ts index 5ac4f3107c4..47aaa6cd083 100644 --- a/packages/bsky/src/api/app/bsky/graph/getRelationships.ts +++ b/packages/bsky/src/api/app/bsky/graph/getRelationships.ts @@ -1,6 +1,5 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { Relationship } from '../../../../lexicon/types/app/bsky/graph/defs' export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.getRelationships({ @@ -15,42 +14,18 @@ export default function (server: Server, ctx: AppContext) { }, } } - const db = ctx.db.getPrimary() - const { ref } = db.db.dynamic - const res = await db.db - .selectFrom('actor') - .select([ - 'actor.did', - db.db - .selectFrom('follow') - .where('creator', '=', actor) - .whereRef('subjectDid', '=', ref('actor.did')) - .select('uri') - .as('following'), - db.db - .selectFrom('follow') - .whereRef('creator', '=', ref('actor.did')) - .where('subjectDid', '=', actor) - .select('uri') - .as('followedBy'), - ]) - .where('actor.did', 'in', others) - .execute() - - const relationshipsMap = res.reduce((acc, cur) => { - return acc.set(cur.did, { - did: cur.did, - following: cur.following ?? undefined, - followedBy: cur.followedBy ?? undefined, - }) - }, new Map()) - + const res = await ctx.hydrator.actor.getProfileViewerStatesNaive( + others, + actor, + ) const relationships = others.map((did) => { - const relationship = relationshipsMap.get(did) - return relationship + const subject = res.get(did) + return subject ? { $type: 'app.bsky.graph.defs#relationship', - ...relationship, + did, + following: subject.following, + followedBy: subject.followedBy, } : { $type: 'app.bsky.graph.defs#notFoundActor', @@ -58,7 +33,6 @@ export default function (server: Server, ctx: AppContext) { notFound: true, } }) - return { encoding: 'application/json', body: { diff --git a/packages/bsky/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts b/packages/bsky/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts index 3aec8ded48e..49c4a61c1c8 100644 --- a/packages/bsky/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts +++ b/packages/bsky/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts @@ -1,139 +1,98 @@ -import { sql } from 'kysely' +import { mapDefined } from '@atproto/common' +import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' +import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/getSuggestedFollowsByActor' import AppContext from '../../../../context' -import { InvalidRequestError } from '@atproto/xrpc-server' -import { Database } from '../../../../db' -import { ActorService } from '../../../../services/actor' - -const RESULT_LENGTH = 10 +import { + HydrationFnInput, + PresentationFnInput, + RulesFnInput, + SkeletonFnInput, + createPipeline, +} from '../../../../pipeline' +import { Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' export default function (server: Server, ctx: AppContext) { + const getSuggestedFollowsByActor = createPipeline( + skeleton, + hydration, + noBlocksOrMutes, + presentation, + ) server.app.bsky.graph.getSuggestedFollowsByActor({ auth: ctx.authVerifier.standard, handler: async ({ auth, params }) => { - const { actor } = params const viewer = auth.credentials.iss - - const db = ctx.db.getReplica() - const actorService = ctx.services.actor(db) - const actorDid = await actorService.getActorDid(actor) - - if (!actorDid) { - throw new InvalidRequestError('Actor not found') - } - - const skeleton = await getSkeleton( - { - actor: actorDid, - viewer, - }, - { - db, - actorService, - }, - ) - const hydrationState = await actorService.views.profileDetailHydration( - skeleton.map((a) => a.did), - { viewer }, - ) - const presentationState = actorService.views.profileDetailPresentation( - skeleton.map((a) => a.did), - hydrationState, - { viewer }, + const result = await getSuggestedFollowsByActor( + { ...params, viewer }, + ctx, ) - const suggestions = Object.values(presentationState).filter((profile) => { - return ( - !profile.viewer?.muted && - !profile.viewer?.mutedByList && - !profile.viewer?.blocking && - !profile.viewer?.blockedBy - ) - }) - return { encoding: 'application/json', - body: { suggestions }, + body: result, } }, }) } -async function getSkeleton( - params: { - actor: string - viewer: string - }, - ctx: { - db: Database - actorService: ActorService - }, -): Promise<{ did: string }[]> { - const actorsViewerFollows = ctx.db.db - .selectFrom('follow') - .where('creator', '=', params.viewer) - .select('subjectDid') - const mostLikedAccounts = await ctx.db.db - .selectFrom( - ctx.db.db - .selectFrom('like') - .where('creator', '=', params.actor) - .select(sql`split_part(subject, '/', 3)`.as('subjectDid')) - .orderBy('sortAt', 'desc') - .limit(1000) // limit to 1000 - .as('likes'), - ) - .select('likes.subjectDid as did') - .select((qb) => qb.fn.count('likes.subjectDid').as('count')) - .where('likes.subjectDid', 'not in', actorsViewerFollows) - .where('likes.subjectDid', 'not in', [params.actor, params.viewer]) - .groupBy('likes.subjectDid') - .orderBy('count', 'desc') - .limit(RESULT_LENGTH) - .execute() - const resultDids = mostLikedAccounts.map((a) => ({ did: a.did })) as { - did: string - }[] +const skeleton = async (input: SkeletonFnInput) => { + const { params, ctx } = input + const [relativeToDid] = await ctx.hydrator.actor.getDids([params.actor]) + if (!relativeToDid) { + throw new InvalidRequestError('Actor not found') + } + const { dids, cursor } = await ctx.hydrator.dataplane.getFollowSuggestions({ + actorDid: params.viewer, + relativeToDid, + }) + return { + suggestedDids: dids, + cursor: cursor || undefined, + } +} + +const hydration = async ( + input: HydrationFnInput, +) => { + const { ctx, params, skeleton } = input + const { viewer } = params + const { suggestedDids } = skeleton + return ctx.hydrator.hydrateProfilesDetailed(suggestedDids, viewer) +} - if (resultDids.length < RESULT_LENGTH) { - // backfill with popular accounts followed by actor - const mostPopularAccountsActorFollows = await ctx.db.db - .selectFrom('follow') - .innerJoin('profile_agg', 'follow.subjectDid', 'profile_agg.did') - .select('follow.subjectDid as did') - .where('follow.creator', '=', params.actor) - .where('follow.subjectDid', '!=', params.viewer) - .where('follow.subjectDid', 'not in', actorsViewerFollows) - .if(resultDids.length > 0, (qb) => - qb.where( - 'subjectDid', - 'not in', - resultDids.map((a) => a.did), - ), - ) - .orderBy('profile_agg.followersCount', 'desc') - .limit(RESULT_LENGTH) - .execute() +const noBlocksOrMutes = ( + input: RulesFnInput, +) => { + const { ctx, skeleton, hydration } = input + skeleton.suggestedDids = skeleton.suggestedDids.filter( + (did) => + !ctx.views.viewerBlockExists(did, hydration) && + !ctx.views.viewerMuteExists(did, hydration), + ) + return skeleton +} - resultDids.push(...mostPopularAccountsActorFollows) - } +const presentation = ( + input: PresentationFnInput, +) => { + const { ctx, hydration, skeleton } = input + const { suggestedDids } = skeleton + const suggestions = mapDefined(suggestedDids, (did) => + ctx.views.profileDetailed(did, hydration), + ) + return { suggestions } +} - if (resultDids.length < RESULT_LENGTH) { - // backfill with suggested_follow table - const additional = await ctx.db.db - .selectFrom('suggested_follow') - .where( - 'did', - 'not in', - // exclude any we already have - resultDids.map((a) => a.did).concat([params.actor, params.viewer]), - ) - // and aren't already followed by viewer - .where('did', 'not in', actorsViewerFollows) - .selectAll() - .execute() +type Context = { + hydrator: Hydrator + views: Views +} - resultDids.push(...additional) - } +type Params = QueryParams & { + viewer: string +} - return resultDids +type SkeletonState = { + suggestedDids: string[] } diff --git a/packages/bsky/src/api/app/bsky/graph/muteActor.ts b/packages/bsky/src/api/app/bsky/graph/muteActor.ts index 72a1635c55d..65600344a8d 100644 --- a/packages/bsky/src/api/app/bsky/graph/muteActor.ts +++ b/packages/bsky/src/api/app/bsky/graph/muteActor.ts @@ -1,9 +1,7 @@ -import assert from 'node:assert' import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { MuteOperation_Type } from '../../../../proto/bsync_pb' -import { BsyncClient } from '../../../../bsync' export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.muteActor({ @@ -11,44 +9,13 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ req, auth, input }) => { const { actor } = input.body const requester = auth.credentials.iss - const db = ctx.db.getPrimary() - - const subjectDid = await ctx.services.actor(db).getActorDid(actor) - if (!subjectDid) { - throw new InvalidRequestError(`Actor not found: ${actor}`) - } - if (subjectDid === requester) { - throw new InvalidRequestError('Cannot mute oneself') - } - - const muteActor = async () => { - await ctx.services.graph(db).muteActor({ - subjectDid, - mutedByDid: requester, - }) - } - - const addBsyncMuteOp = async (bsyncClient: BsyncClient) => { - await bsyncClient.addMuteOperation({ - type: MuteOperation_Type.ADD, - actorDid: requester, - subject: subjectDid, - }) - } - - if (ctx.cfg.bsyncOnlyMutes) { - assert(ctx.bsyncClient) - await addBsyncMuteOp(ctx.bsyncClient) - } else { - await muteActor() - if (ctx.bsyncClient) { - try { - await addBsyncMuteOp(ctx.bsyncClient) - } catch (err) { - req.log.warn(err, 'failed to sync mute op to bsync') - } - } - } + const [did] = await ctx.hydrator.actor.getDids([actor]) + if (!did) throw new InvalidRequestError('Actor not found') + await ctx.bsyncClient.addMuteOperation({ + type: MuteOperation_Type.ADD, + actorDid: requester, + subject: did, + }) }, }) } diff --git a/packages/bsky/src/api/app/bsky/graph/muteActorList.ts b/packages/bsky/src/api/app/bsky/graph/muteActorList.ts index f2ee8bfeea0..2f9f8c7573f 100644 --- a/packages/bsky/src/api/app/bsky/graph/muteActorList.ts +++ b/packages/bsky/src/api/app/bsky/graph/muteActorList.ts @@ -1,55 +1,18 @@ -import assert from 'node:assert' -import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' -import * as lex from '../../../../lexicon/lexicons' import AppContext from '../../../../context' -import { AtUri } from '@atproto/syntax' import { MuteOperation_Type } from '../../../../proto/bsync_pb' -import { BsyncClient } from '../../../../bsync' export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.muteActorList({ auth: ctx.authVerifier.standard, - handler: async ({ req, auth, input }) => { + handler: async ({ auth, input }) => { const { list } = input.body const requester = auth.credentials.iss - - const db = ctx.db.getPrimary() - - const listUri = new AtUri(list) - const collId = lex.ids.AppBskyGraphList - if (listUri.collection !== collId) { - throw new InvalidRequestError(`Invalid collection: expected: ${collId}`) - } - - const muteActorList = async () => { - await ctx.services.graph(db).muteActorList({ - list, - mutedByDid: requester, - }) - } - - const addBsyncMuteOp = async (bsyncClient: BsyncClient) => { - await bsyncClient.addMuteOperation({ - type: MuteOperation_Type.ADD, - actorDid: requester, - subject: list, - }) - } - - if (ctx.cfg.bsyncOnlyMutes) { - assert(ctx.bsyncClient) - await addBsyncMuteOp(ctx.bsyncClient) - } else { - await muteActorList() - if (ctx.bsyncClient) { - try { - await addBsyncMuteOp(ctx.bsyncClient) - } catch (err) { - req.log.warn(err, 'failed to sync mute op to bsync') - } - } - } + await ctx.bsyncClient.addMuteOperation({ + type: MuteOperation_Type.ADD, + actorDid: requester, + subject: list, + }) }, }) } diff --git a/packages/bsky/src/api/app/bsky/graph/unmuteActor.ts b/packages/bsky/src/api/app/bsky/graph/unmuteActor.ts index e35e00202ef..5462d7a7117 100644 --- a/packages/bsky/src/api/app/bsky/graph/unmuteActor.ts +++ b/packages/bsky/src/api/app/bsky/graph/unmuteActor.ts @@ -1,54 +1,21 @@ -import assert from 'node:assert' import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { MuteOperation_Type } from '../../../../proto/bsync_pb' -import { BsyncClient } from '../../../../bsync' export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.unmuteActor({ auth: ctx.authVerifier.standard, - handler: async ({ req, auth, input }) => { + handler: async ({ auth, input }) => { const { actor } = input.body const requester = auth.credentials.iss - const db = ctx.db.getPrimary() - - const subjectDid = await ctx.services.actor(db).getActorDid(actor) - if (!subjectDid) { - throw new InvalidRequestError(`Actor not found: ${actor}`) - } - if (subjectDid === requester) { - throw new InvalidRequestError('Cannot mute oneself') - } - - const unmuteActor = async () => { - await ctx.services.graph(db).unmuteActor({ - subjectDid, - mutedByDid: requester, - }) - } - - const addBsyncMuteOp = async (bsyncClient: BsyncClient) => { - await bsyncClient.addMuteOperation({ - type: MuteOperation_Type.REMOVE, - actorDid: requester, - subject: subjectDid, - }) - } - - if (ctx.cfg.bsyncOnlyMutes) { - assert(ctx.bsyncClient) - await addBsyncMuteOp(ctx.bsyncClient) - } else { - await unmuteActor() - if (ctx.bsyncClient) { - try { - await addBsyncMuteOp(ctx.bsyncClient) - } catch (err) { - req.log.warn(err, 'failed to sync mute op to bsync') - } - } - } + const [did] = await ctx.hydrator.actor.getDids([actor]) + if (!did) throw new InvalidRequestError('Actor not found') + await ctx.bsyncClient.addMuteOperation({ + type: MuteOperation_Type.REMOVE, + actorDid: requester, + subject: did, + }) }, }) } diff --git a/packages/bsky/src/api/app/bsky/graph/unmuteActorList.ts b/packages/bsky/src/api/app/bsky/graph/unmuteActorList.ts index 18d612d7c95..2c80e42187f 100644 --- a/packages/bsky/src/api/app/bsky/graph/unmuteActorList.ts +++ b/packages/bsky/src/api/app/bsky/graph/unmuteActorList.ts @@ -1,45 +1,18 @@ -import assert from 'node:assert' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { MuteOperation_Type } from '../../../../proto/bsync_pb' -import { BsyncClient } from '../../../../bsync' export default function (server: Server, ctx: AppContext) { server.app.bsky.graph.unmuteActorList({ auth: ctx.authVerifier.standard, - handler: async ({ req, auth, input }) => { + handler: async ({ auth, input }) => { const { list } = input.body const requester = auth.credentials.iss - const db = ctx.db.getPrimary() - - const unmuteActorList = async () => { - await ctx.services.graph(db).unmuteActorList({ - list, - mutedByDid: requester, - }) - } - - const addBsyncMuteOp = async (bsyncClient: BsyncClient) => { - await bsyncClient.addMuteOperation({ - type: MuteOperation_Type.REMOVE, - actorDid: requester, - subject: list, - }) - } - - if (ctx.cfg.bsyncOnlyMutes) { - assert(ctx.bsyncClient) - await addBsyncMuteOp(ctx.bsyncClient) - } else { - await unmuteActorList() - if (ctx.bsyncClient) { - try { - await addBsyncMuteOp(ctx.bsyncClient) - } catch (err) { - req.log.warn(err, 'failed to sync mute op to bsync') - } - } - } + await ctx.bsyncClient.addMuteOperation({ + type: MuteOperation_Type.REMOVE, + actorDid: requester, + subject: list, + }) }, }) } diff --git a/packages/bsky/src/api/app/bsky/notification/getUnreadCount.ts b/packages/bsky/src/api/app/bsky/notification/getUnreadCount.ts index 71391457902..afbb5b06780 100644 --- a/packages/bsky/src/api/app/bsky/notification/getUnreadCount.ts +++ b/packages/bsky/src/api/app/bsky/notification/getUnreadCount.ts @@ -1,43 +1,74 @@ -import { sql } from 'kysely' import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' -import { countAll, notSoftDeletedClause } from '../../../../db/util' +import { QueryParams } from '../../../../lexicon/types/app/bsky/notification/getUnreadCount' import AppContext from '../../../../context' +import { + HydrationFnInput, + PresentationFnInput, + SkeletonFnInput, + createPipeline, + noRules, +} from '../../../../pipeline' +import { Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' export default function (server: Server, ctx: AppContext) { + const getUnreadCount = createPipeline( + skeleton, + hydration, + noRules, + presentation, + ) server.app.bsky.notification.getUnreadCount({ auth: ctx.authVerifier.standard, handler: async ({ auth, params }) => { - const requester = auth.credentials.iss - if (params.seenAt) { - throw new InvalidRequestError('The seenAt parameter is unsupported') - } - - const db = ctx.db.getReplica() - const { ref } = db.db.dynamic - const result = await db.db - .selectFrom('notification') - .select(countAll.as('count')) - .innerJoin('actor', 'actor.did', 'notification.did') - .leftJoin('actor_state', 'actor_state.did', 'actor.did') - .innerJoin('record', 'record.uri', 'notification.recordUri') - .where(notSoftDeletedClause(ref('actor'))) - .where(notSoftDeletedClause(ref('record'))) - // Ensure to hit notification_did_sortat_idx, handling case where lastSeenNotifs is null. - .where('notification.did', '=', requester) - .where( - 'notification.sortAt', - '>', - sql`coalesce(${ref('actor_state.lastSeenNotifs')}, ${''})`, - ) - .executeTakeFirst() - - const count = result?.count ?? 0 - + const viewer = auth.credentials.iss + const result = await getUnreadCount({ ...params, viewer }, ctx) return { encoding: 'application/json', - body: { count }, + body: result, } }, }) } + +const skeleton = async ( + input: SkeletonFnInput, +): Promise => { + const { params, ctx } = input + if (params.seenAt) { + throw new InvalidRequestError('The seenAt parameter is unsupported') + } + const res = await ctx.hydrator.dataplane.getUnreadNotificationCount({ + actorDid: params.viewer, + }) + return { + count: res.count, + } +} + +const hydration = async ( + _input: HydrationFnInput, +) => { + return {} +} + +const presentation = ( + input: PresentationFnInput, +) => { + const { skeleton } = input + return { count: skeleton.count } +} + +type Context = { + hydrator: Hydrator + views: Views +} + +type Params = QueryParams & { + viewer: string +} + +type SkeletonState = { + count: number +} diff --git a/packages/bsky/src/api/app/bsky/notification/listNotifications.ts b/packages/bsky/src/api/app/bsky/notification/listNotifications.ts index ce5274a9da7..6f738fb4cca 100644 --- a/packages/bsky/src/api/app/bsky/notification/listNotifications.ts +++ b/packages/bsky/src/api/app/bsky/notification/listNotifications.ts @@ -1,16 +1,20 @@ import { InvalidRequestError } from '@atproto/xrpc-server' -import { jsonStringToLex } from '@atproto/lexicon' import { mapDefined } from '@atproto/common' import { Server } from '../../../../lexicon' import { QueryParams } from '../../../../lexicon/types/app/bsky/notification/listNotifications' import AppContext from '../../../../context' -import { Database } from '../../../../db' -import { notSoftDeletedClause } from '../../../../db/util' -import { paginate, TimeCidKeyset } from '../../../../db/pagination' -import { BlockAndMuteState, GraphService } from '../../../../services/graph' -import { ActorInfoMap, ActorService } from '../../../../services/actor' -import { getSelfLabels, Labels, LabelService } from '../../../../services/label' -import { createPipeline } from '../../../../pipeline' +import { + createPipeline, + HydrationFnInput, + PresentationFnInput, + RulesFnInput, + SkeletonFnInput, +} from '../../../../pipeline' +import { Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { Notification } from '../../../../proto/bsky_pb' +import { didFromUri } from '../../../../hydration/util' +import { clearlyBadCursor } from '../../../util' export default function (server: Server, ctx: AppContext) { const listNotifications = createPipeline( @@ -22,17 +26,8 @@ export default function (server: Server, ctx: AppContext) { server.app.bsky.notification.listNotifications({ auth: ctx.authVerifier.standard, handler: async ({ params, auth }) => { - const db = ctx.db.getReplica() - const actorService = ctx.services.actor(db) - const graphService = ctx.services.graph(db) - const labelService = ctx.services.label(db) const viewer = auth.credentials.iss - - const result = await listNotifications( - { ...params, viewer }, - { db, actorService, graphService, labelService }, - ) - + const result = await listNotifications({ ...params, viewer }, ctx) return { encoding: 'application/json', body: result, @@ -42,141 +37,73 @@ export default function (server: Server, ctx: AppContext) { } const skeleton = async ( - params: Params, - ctx: Context, + input: SkeletonFnInput, ): Promise => { - const { db } = ctx - const { limit, cursor, viewer } = params - const { ref } = db.db.dynamic + const { params, ctx } = input if (params.seenAt) { throw new InvalidRequestError('The seenAt parameter is unsupported') } - if (NotifsKeyset.clearlyBad(cursor)) { - return { params, notifs: [] } + if (clearlyBadCursor(params.cursor)) { + return { notifs: [] } } - let notifBuilder = db.db - .selectFrom('notification as notif') - .where('notif.did', '=', viewer) - .where((clause) => - clause - .where('reasonSubject', 'is', null) - .orWhereExists( - db.db - .selectFrom('record as subject') - .selectAll() - .whereRef('subject.uri', '=', ref('notif.reasonSubject')), - ), - ) - .select([ - 'notif.author as authorDid', - 'notif.recordUri as uri', - 'notif.recordCid as cid', - 'notif.reason as reason', - 'notif.reasonSubject as reasonSubject', - 'notif.sortAt as indexedAt', - ]) - - const keyset = new NotifsKeyset(ref('notif.sortAt'), ref('notif.recordCid')) - notifBuilder = paginate(notifBuilder, { - cursor, - limit, - keyset, - tryIndex: true, - }) - - const actorStateQuery = db.db - .selectFrom('actor_state') - .selectAll() - .where('did', '=', viewer) - - const [notifs, actorState] = await Promise.all([ - notifBuilder.execute(), - actorStateQuery.executeTakeFirst(), + const [res, lastSeenRes] = await Promise.all([ + ctx.hydrator.dataplane.getNotifications({ + actorDid: params.viewer, + cursor: params.cursor, + limit: params.limit, + }), + ctx.hydrator.dataplane.getNotificationSeen({ + actorDid: params.viewer, + }), ]) - + // @NOTE for the first page of results if there's no last-seen time, consider top notification unread + // rather than all notifications. bit of a hack to be more graceful when seen times are out of sync. + let lastSeenDate = lastSeenRes.timestamp?.toDate() + if (!lastSeenDate && !params.cursor) { + lastSeenDate = res.notifications.at(0)?.timestamp?.toDate() + } return { - params, - notifs, - cursor: keyset.packFromResult(notifs), - lastSeenNotifs: actorState?.lastSeenNotifs, + notifs: res.notifications, + cursor: res.cursor || undefined, + lastSeenNotifs: lastSeenDate?.toISOString(), } } -const hydration = async (state: SkeletonState, ctx: Context) => { - const { graphService, actorService, labelService, db } = ctx - const { params, notifs } = state - const { viewer } = params - const dids = notifs.map((notif) => notif.authorDid) - const uris = notifs.map((notif) => notif.uri) - const [actors, records, labels, bam] = await Promise.all([ - actorService.views.profiles(dids, viewer), - getRecordMap(db, uris), - labelService.getLabelsForUris(uris), - graphService.getBlockAndMuteState(dids.map((did) => [viewer, did])), - ]) - return { ...state, actors, records, labels, bam } +const hydration = async ( + input: HydrationFnInput, +) => { + const { skeleton, params, ctx } = input + return ctx.hydrator.hydrateNotifications(skeleton.notifs, params.viewer) } -const noBlockOrMutes = (state: HydrationState) => { - const { viewer } = state.params - state.notifs = state.notifs.filter( - (item) => - !state.bam.block([viewer, item.authorDid]) && - !state.bam.mute([viewer, item.authorDid]), - ) - return state -} - -const presentation = (state: HydrationState) => { - const { notifs, cursor, actors, records, labels, lastSeenNotifs } = state - const notifications = mapDefined(notifs, (notif) => { - const author = actors[notif.authorDid] - const record = records[notif.uri] - if (!author || !record) return undefined - const recordLabels = labels[notif.uri] ?? [] - const recordSelfLabels = getSelfLabels({ - uri: notif.uri, - cid: notif.cid, - record, - }) - return { - uri: notif.uri, - cid: notif.cid, - author, - reason: notif.reason, - reasonSubject: notif.reasonSubject || undefined, - record, - isRead: lastSeenNotifs ? notif.indexedAt <= lastSeenNotifs : false, - indexedAt: notif.indexedAt, - labels: [...recordLabels, ...recordSelfLabels], - } +const noBlockOrMutes = ( + input: RulesFnInput, +) => { + const { skeleton, hydration, ctx } = input + skeleton.notifs = skeleton.notifs.filter((item) => { + const did = didFromUri(item.uri) + return ( + !ctx.views.viewerBlockExists(did, hydration) && + !ctx.views.viewerMuteExists(did, hydration) + ) }) - return { notifications, cursor, seenAt: lastSeenNotifs } + return skeleton } -const getRecordMap = async ( - db: Database, - uris: string[], -): Promise => { - if (!uris.length) return {} - const { ref } = db.db.dynamic - const recordRows = await db.db - .selectFrom('record') - .select(['uri', 'json']) - .where('uri', 'in', uris) - .where(notSoftDeletedClause(ref('record'))) - .execute() - return recordRows.reduce((acc, { uri, json }) => { - acc[uri] = jsonStringToLex(json) as Record - return acc - }, {} as RecordMap) +const presentation = ( + input: PresentationFnInput, +) => { + const { skeleton, hydration, ctx } = input + const { notifs, lastSeenNotifs, cursor } = skeleton + const notifications = mapDefined(notifs, (notif) => + ctx.views.notification(notif, lastSeenNotifs, hydration), + ) + return { notifications, cursor, seenAt: skeleton.lastSeenNotifs } } type Context = { - db: Database - actorService: ActorService - graphService: GraphService - labelService: LabelService + hydrator: Hydrator + views: Views } type Params = QueryParams & { @@ -184,32 +111,7 @@ type Params = QueryParams & { } type SkeletonState = { - params: Params - notifs: NotifRow[] + notifs: Notification[] lastSeenNotifs?: string cursor?: string } - -type HydrationState = SkeletonState & { - bam: BlockAndMuteState - actors: ActorInfoMap - records: RecordMap - labels: Labels -} - -type RecordMap = { [uri: string]: Record } - -type NotifRow = { - authorDid: string - uri: string - cid: string - reason: string - reasonSubject: string | null - indexedAt: string -} - -class NotifsKeyset extends TimeCidKeyset { - labelResult(result: NotifRow) { - return { primary: result.indexedAt, secondary: result.cid } - } -} diff --git a/packages/bsky/src/api/app/bsky/notification/registerPush.ts b/packages/bsky/src/api/app/bsky/notification/registerPush.ts index abce1cd096c..bd4bcec1f2b 100644 --- a/packages/bsky/src/api/app/bsky/notification/registerPush.ts +++ b/packages/bsky/src/api/app/bsky/notification/registerPush.ts @@ -1,15 +1,12 @@ -import assert from 'node:assert' import { InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { Platform } from '../../../../notifications' -import { CourierClient } from '../../../../courier' import { AppPlatform } from '../../../../proto/courier_pb' export default function (server: Server, ctx: AppContext) { server.app.bsky.notification.registerPush({ auth: ctx.authVerifier.standard, - handler: async ({ req, auth, input }) => { + handler: async ({ auth, input }) => { const { token, platform, serviceDid, appId } = input.body const did = auth.credentials.iss if (serviceDid !== auth.credentials.aud) { @@ -20,44 +17,17 @@ export default function (server: Server, ctx: AppContext) { 'Unsupported platform: must be "ios", "android", or "web".', ) } - - const db = ctx.db.getPrimary() - - const registerDeviceWithAppview = async () => { - await ctx.services - .actor(db) - .registerPushDeviceToken(did, token, platform as Platform, appId) - } - - const registerDeviceWithCourier = async ( - courierClient: CourierClient, - ) => { - await courierClient.registerDeviceToken({ - did, - token, - platform: - platform === 'ios' - ? AppPlatform.IOS - : platform === 'android' - ? AppPlatform.ANDROID - : AppPlatform.WEB, - appId, - }) - } - - if (ctx.cfg.courierOnlyRegistration) { - assert(ctx.courierClient) - await registerDeviceWithCourier(ctx.courierClient) - } else { - await registerDeviceWithAppview() - if (ctx.courierClient) { - try { - await registerDeviceWithCourier(ctx.courierClient) - } catch (err) { - req.log.warn(err, 'failed to register device token with courier') - } - } - } + await ctx.courierClient.registerDeviceToken({ + did, + token, + platform: + platform === 'ios' + ? AppPlatform.IOS + : platform === 'android' + ? AppPlatform.ANDROID + : AppPlatform.WEB, + appId, + }) }, }) } diff --git a/packages/bsky/src/api/app/bsky/notification/updateSeen.ts b/packages/bsky/src/api/app/bsky/notification/updateSeen.ts index 4b8b614fbad..4c9e66113d6 100644 --- a/packages/bsky/src/api/app/bsky/notification/updateSeen.ts +++ b/packages/bsky/src/api/app/bsky/notification/updateSeen.ts @@ -1,7 +1,6 @@ +import { Timestamp } from '@bufbuild/protobuf' import { Server } from '../../../../lexicon' -import { InvalidRequestError } from '@atproto/xrpc-server' import AppContext from '../../../../context' -import { excluded } from '../../../../db/util' export default function (server: Server, ctx: AppContext) { server.app.bsky.notification.updateSeen({ @@ -9,25 +8,10 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ input, auth }) => { const { seenAt } = input.body const viewer = auth.credentials.iss - - let parsed: string - try { - parsed = new Date(seenAt).toISOString() - } catch (_err) { - throw new InvalidRequestError('Invalid date') - } - - const db = ctx.db.getPrimary() - - await db.db - .insertInto('actor_state') - .values({ did: viewer, lastSeenNotifs: parsed }) - .onConflict((oc) => - oc.column('did').doUpdateSet({ - lastSeenNotifs: excluded(db.db, 'lastSeenNotifs'), - }), - ) - .executeTakeFirst() + await ctx.dataplane.updateNotificationSeen({ + actorDid: viewer, + timestamp: Timestamp.fromDate(new Date(seenAt)), + }) }, }) } diff --git a/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts b/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts index c15a5242b0f..a5a3d8a8cef 100644 --- a/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +++ b/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts @@ -1,107 +1,57 @@ +import { mapDefined } from '@atproto/common' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { countAll } from '../../../../db/util' -import { GenericKeyset, paginate } from '../../../../db/pagination' -import { InvalidRequestError } from '@atproto/xrpc-server' -import { GeneratorView } from '../../../../lexicon/types/app/bsky/feed/defs' +import { parseString } from '../../../../hydration/util' +import { clearlyBadCursor } from '../../../util' // THIS IS A TEMPORARY UNSPECCED ROUTE +// @TODO currently mirrors getSuggestedFeeds and ignores the "query" param. +// In the future may take into consideration popularity via likes w/ its own dataplane endpoint. export default function (server: Server, ctx: AppContext) { server.app.bsky.unspecced.getPopularFeedGenerators({ auth: ctx.authVerifier.standardOptional, handler: async ({ auth, params }) => { - const { limit, cursor, query } = params - const requester = auth.credentials.iss - if (LikeCountKeyset.clearlyBad(cursor)) { + const viewer = auth.credentials.iss + + if (clearlyBadCursor(params.cursor)) { return { encoding: 'application/json', body: { feeds: [] }, } } - const db = ctx.db.getReplica() - const { ref } = db.db.dynamic - const feedService = ctx.services.feed(db) - const actorService = ctx.services.actor(db) - let inner = db.db - .selectFrom('feed_generator') - .select([ - 'uri', - 'cid', - db.db - .selectFrom('like') - .whereRef('like.subject', '=', ref('feed_generator.uri')) - .select(countAll.as('count')) - .as('likeCount'), - ]) + let uris: string[] + let cursor: string | undefined + const query = params.query?.trim() ?? '' if (query) { - inner = inner.where((qb) => - qb - .where('feed_generator.displayName', 'ilike', `%${query}%`) - .orWhere('feed_generator.description', 'ilike', `%${query}%`), - ) + const res = await ctx.dataplane.searchFeedGenerators({ + query, + limit: params.limit, + }) + uris = res.uris + } else { + const res = await ctx.dataplane.getSuggestedFeeds({ + actorDid: viewer ?? undefined, + limit: params.limit, + cursor: params.cursor, + }) + uris = res.uris + cursor = parseString(res.cursor) } - let builder = db.db.selectFrom(inner.as('feed_gens')).selectAll() - - const keyset = new LikeCountKeyset(ref('likeCount'), ref('cid')) - builder = paginate(builder, { limit, cursor, keyset, direction: 'desc' }) - - const res = await builder.execute() - - const genInfos = await feedService.getFeedGeneratorInfos( - res.map((feed) => feed.uri), - requester, + const hydration = await ctx.hydrator.hydrateFeedGens(uris, viewer) + const feedViews = mapDefined(uris, (uri) => + ctx.views.feedGenerator(uri, hydration), ) - const creators = Object.values(genInfos).map((gen) => gen.creator) - const profiles = await actorService.views.profiles(creators, requester) - - const genViews: GeneratorView[] = [] - for (const row of res) { - const gen = genInfos[row.uri] - if (!gen) continue - const view = feedService.views.formatFeedGeneratorView(gen, profiles) - if (view) { - genViews.push(view) - } - } - return { encoding: 'application/json', body: { - cursor: keyset.packFromResult(res), - feeds: genViews, + feeds: feedViews, + cursor, }, } }, }) } - -type Result = { likeCount: number; cid: string } -type LabeledResult = { primary: number; secondary: string } -export class LikeCountKeyset extends GenericKeyset { - labelResult(result: Result) { - return { - primary: result.likeCount, - secondary: result.cid, - } - } - labeledResultToCursor(labeled: LabeledResult) { - return { - primary: labeled.primary.toString(), - secondary: labeled.secondary, - } - } - cursorToLabeledResult(cursor: { primary: string; secondary: string }) { - const likes = parseInt(cursor.primary, 10) - if (isNaN(likes)) { - throw new InvalidRequestError('Malformed cursor') - } - return { - primary: likes, - secondary: cursor.secondary, - } - } -} diff --git a/packages/bsky/src/api/app/bsky/unspecced/getTaggedSuggestions.ts b/packages/bsky/src/api/app/bsky/unspecced/getTaggedSuggestions.ts index 4fd248e87ff..79df4857945 100644 --- a/packages/bsky/src/api/app/bsky/unspecced/getTaggedSuggestions.ts +++ b/packages/bsky/src/api/app/bsky/unspecced/getTaggedSuggestions.ts @@ -5,11 +5,12 @@ import AppContext from '../../../../context' export default function (server: Server, ctx: AppContext) { server.app.bsky.unspecced.getTaggedSuggestions({ handler: async () => { - const suggestions = await ctx.db - .getReplica() - .db.selectFrom('tagged_suggestion') - .selectAll() - .execute() + const res = await ctx.dataplane.getSuggestedEntities({}) + const suggestions = res.entities.map((entity) => ({ + tag: entity.tag, + subjectType: entity.subjectType, + subject: entity.subject, + })) return { encoding: 'application/json', body: { diff --git a/packages/bsky/src/api/app/bsky/util/feed.ts b/packages/bsky/src/api/app/bsky/util/feed.ts deleted file mode 100644 index 769b2d7e833..00000000000 --- a/packages/bsky/src/api/app/bsky/util/feed.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { TimeCidKeyset } from '../../../../db/pagination' -import { FeedRow } from '../../../../services/feed/types' - -export enum FeedAlgorithm { - ReverseChronological = 'reverse-chronological', -} - -export class FeedKeyset extends TimeCidKeyset { - labelResult(result: FeedRow) { - return { primary: result.sortAt, secondary: result.cid } - } -} - -// For users with sparse feeds, avoid scanning more than one week for a single page -export const getFeedDateThreshold = (from: string | undefined, days = 1) => { - const timelineDateThreshold = from ? new Date(from) : new Date() - timelineDateThreshold.setDate(timelineDateThreshold.getDate() - days) - return timelineDateThreshold.toISOString() -} diff --git a/packages/bsky/src/api/blob-resolver.ts b/packages/bsky/src/api/blob-resolver.ts index c307152c43a..1a2d1ee560d 100644 --- a/packages/bsky/src/api/blob-resolver.ts +++ b/packages/bsky/src/api/blob-resolver.ts @@ -5,11 +5,16 @@ import axios, { AxiosError } from 'axios' import { CID } from 'multiformats/cid' import { ensureValidDid } from '@atproto/syntax' import { forwardStreamErrors, VerifyCidTransform } from '@atproto/common' -import { IdResolver, DidNotFoundError } from '@atproto/identity' +import { DidNotFoundError } from '@atproto/identity' import AppContext from '../context' import { httpLogger as log } from '../logger' import { retryHttp } from '../util/retry' -import { Database } from '../db' +import { + Code, + getServiceEndpoint, + isDataplaneError, + unpackIdentityServices, +} from '../data-plane' // Resolve and verify blob from its origin host @@ -31,8 +36,7 @@ export const createRouter = (ctx: AppContext): express.Router => { return next(createError(400, 'Invalid cid')) } - const db = ctx.db.getReplica() - const verifiedImage = await resolveBlob(did, cid, db, ctx.idResolver) + const verifiedImage = await resolveBlob(ctx, did, cid) // Send chunked response, destroying stream early (before // closing chunk) if the bytes don't match the expected cid. @@ -76,24 +80,29 @@ export const createRouter = (ctx: AppContext): express.Router => { return router } -export async function resolveBlob( - did: string, - cid: CID, - db: Database, - idResolver: IdResolver, -) { +export async function resolveBlob(ctx: AppContext, did: string, cid: CID) { const cidStr = cid.toString() - const [{ pds }, takedown] = await Promise.all([ - idResolver.did.resolveAtprotoData(did), // @TODO cache did info - db.db - .selectFrom('blob_takedown') - .select('takedownRef') - .where('did', '=', did) - .where('cid', '=', cid.toString()) - .executeTakeFirst(), + const [identity, { takenDown }] = await Promise.all([ + ctx.dataplane.getIdentityByDid({ did }).catch((err) => { + if (isDataplaneError(err, Code.NotFound)) { + return undefined + } + throw err + }), + ctx.dataplane.getBlobTakedown({ did, cid: cid.toString() }), ]) - if (takedown) { + const services = identity && unpackIdentityServices(identity.services) + const pds = + services && + getServiceEndpoint(services, { + id: 'atproto_pds', + type: 'AtprotoPersonalDataServer', + }) + if (!pds) { + throw createError(404, 'Origin not found') + } + if (takenDown) { throw createError(404, 'Blob not found') } diff --git a/packages/bsky/src/api/com/atproto/admin/getAccountInfos.ts b/packages/bsky/src/api/com/atproto/admin/getAccountInfos.ts index 9ef66c94c9b..e01be2d6383 100644 --- a/packages/bsky/src/api/com/atproto/admin/getAccountInfos.ts +++ b/packages/bsky/src/api/com/atproto/admin/getAccountInfos.ts @@ -1,6 +1,5 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { Actor } from '../../../../db/tables/actor' import { mapDefined } from '@atproto/common' import { INVALID_HANDLE } from '@atproto/syntax' @@ -9,25 +8,16 @@ export default function (server: Server, ctx: AppContext) { auth: ctx.authVerifier.roleOrAdminService, handler: async ({ params }) => { const { dids } = params - const db = ctx.db.getPrimary() - const actorService = ctx.services.actor(db) - const [actors, profiles] = await Promise.all([ - actorService.getActors(dids, true), - actorService.getProfileRecords(dids, true), - ]) - const actorByDid = actors.reduce((acc, cur) => { - return acc.set(cur.did, cur) - }, new Map()) + const actors = await ctx.hydrator.actor.getActors(dids, true) const infos = mapDefined(dids, (did) => { - const info = actorByDid.get(did) + const info = actors.get(did) if (!info) return - const profile = profiles.get(did) return { did, handle: info.handle ?? INVALID_HANDLE, - relatedRecords: profile ? [profile] : undefined, - indexedAt: info.indexedAt, + relatedRecords: info.profile ? [info.profile] : undefined, + indexedAt: (info.sortedAt ?? new Date(0)).toISOString(), } }) diff --git a/packages/bsky/src/api/com/atproto/admin/getSubjectStatus.ts b/packages/bsky/src/api/com/atproto/admin/getSubjectStatus.ts index 8ac237240f9..2ca7bcdc2c9 100644 --- a/packages/bsky/src/api/com/atproto/admin/getSubjectStatus.ts +++ b/packages/bsky/src/api/com/atproto/admin/getSubjectStatus.ts @@ -8,7 +8,7 @@ export default function (server: Server, ctx: AppContext) { auth: ctx.authVerifier.roleOrAdminService, handler: async ({ params }) => { const { did, uri, blob } = params - const modService = ctx.services.moderation(ctx.db.getPrimary()) + let body: OutputSchema | null = null if (blob) { if (!did) { @@ -16,46 +16,48 @@ export default function (server: Server, ctx: AppContext) { 'Must provide a did to request blob state', ) } - const takedown = await modService.getBlobTakedownRef(did, blob) - if (takedown) { - body = { - subject: { - $type: 'com.atproto.admin.defs#repoBlobRef', - did: did, - cid: blob, - }, - takedown, - } + const res = await ctx.dataplane.getBlobTakedown({ + did, + cid: blob, + }) + body = { + subject: { + $type: 'com.atproto.admin.defs#repoBlobRef', + did: did, + cid: blob, + }, + takedown: { + applied: res.takenDown, + ref: res.takedownRef ? 'TAKEDOWN' : undefined, + }, } } else if (uri) { - const [takedown, cidRes] = await Promise.all([ - modService.getRecordTakedownRef(uri), - ctx.db - .getPrimary() - .db.selectFrom('record') - .where('uri', '=', uri) - .select('cid') - .executeTakeFirst(), - ]) - if (cidRes && takedown) { + const res = await ctx.hydrator.getRecord(uri, true) + if (res) { body = { subject: { $type: 'com.atproto.repo.strongRef', uri, - cid: cidRes.cid, + cid: res.cid, + }, + takedown: { + applied: !!res.takedownRef, + ref: res.takedownRef || undefined, }, - takedown, } } } else if (did) { - const takedown = await modService.getRepoTakedownRef(did) - if (takedown) { + const res = (await ctx.hydrator.actor.getActors([did], true)).get(did) + if (res) { body = { subject: { $type: 'com.atproto.admin.defs#repoRef', did: did, }, - takedown, + takedown: { + applied: !!res.takedownRef, + ref: res.takedownRef || undefined, + }, } } } else { diff --git a/packages/bsky/src/api/com/atproto/admin/updateSubjectStatus.ts b/packages/bsky/src/api/com/atproto/admin/updateSubjectStatus.ts index a7875280137..bb78832aa93 100644 --- a/packages/bsky/src/api/com/atproto/admin/updateSubjectStatus.ts +++ b/packages/bsky/src/api/com/atproto/admin/updateSubjectStatus.ts @@ -1,4 +1,5 @@ -import { AtUri } from '@atproto/syntax' +import { Timestamp } from '@bufbuild/protobuf' +import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' import { @@ -6,8 +7,6 @@ import { isRepoBlobRef, } from '../../../../lexicon/types/com/atproto/admin/defs' import { isMain as isStrongRef } from '../../../../lexicon/types/com/atproto/repo/strongRef' -import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server' -import { CID } from 'multiformats/cid' export default function (server: Server, ctx: AppContext) { server.com.atproto.admin.updateSubjectStatus({ @@ -19,43 +18,49 @@ export default function (server: Server, ctx: AppContext) { 'Must be a full moderator to update subject state', ) } - - const modService = ctx.services.moderation(ctx.db.getPrimary()) - + const now = new Date() const { subject, takedown } = input.body if (takedown) { if (isRepoRef(subject)) { - const did = subject.did if (takedown.applied) { - await modService.takedownRepo({ - takedownRef: takedown.ref ?? new Date().toISOString(), - did, + await ctx.dataplane.takedownActor({ + did: subject.did, + ref: takedown.ref, + seen: Timestamp.fromDate(now), }) } else { - await modService.reverseTakedownRepo({ did }) + await ctx.dataplane.untakedownActor({ + did: subject.did, + seen: Timestamp.fromDate(now), + }) } } else if (isStrongRef(subject)) { - const uri = new AtUri(subject.uri) - const cid = CID.parse(subject.cid) if (takedown.applied) { - await modService.takedownRecord({ - takedownRef: takedown.ref ?? new Date().toISOString(), - uri, - cid, + await ctx.dataplane.takedownRecord({ + recordUri: subject.uri, + ref: takedown.ref, + seen: Timestamp.fromDate(now), }) } else { - await modService.reverseTakedownRecord({ uri }) + await ctx.dataplane.untakedownRecord({ + recordUri: subject.uri, + seen: Timestamp.fromDate(now), + }) } } else if (isRepoBlobRef(subject)) { - const { did, cid } = subject if (takedown.applied) { - await modService.takedownBlob({ - takedownRef: takedown.ref ?? new Date().toISOString(), - did, - cid, + await ctx.dataplane.takedownBlob({ + did: subject.did, + cid: subject.cid, + ref: takedown.ref, + seen: Timestamp.fromDate(now), }) } else { - await modService.reverseTakedownBlob({ did, cid }) + await ctx.dataplane.untakedownBlob({ + did: subject.did, + cid: subject.cid, + seen: Timestamp.fromDate(now), + }) } } else { throw new InvalidRequestError('Invalid subject') diff --git a/packages/bsky/src/api/com/atproto/identity/resolveHandle.ts b/packages/bsky/src/api/com/atproto/identity/resolveHandle.ts index 30c1d7f8a6f..6cb524c6ec2 100644 --- a/packages/bsky/src/api/com/atproto/identity/resolveHandle.ts +++ b/packages/bsky/src/api/com/atproto/identity/resolveHandle.ts @@ -7,12 +7,9 @@ export default function (server: Server, ctx: AppContext) { server.com.atproto.identity.resolveHandle(async ({ req, params }) => { const handle = ident.normalizeHandle(params.handle || req.hostname) - const db = ctx.db.getReplica() - let did: string | undefined - const user = await ctx.services.actor(db).getActor(handle, true) - if (user) { - did = user.did - } else { + let [did] = await ctx.hydrator.actor.getDids([handle]) + + if (!did) { const publicHostname = ctx.cfg.publicUrl ? new URL(ctx.cfg.publicUrl).hostname : null diff --git a/packages/bsky/src/api/com/atproto/repo/getRecord.ts b/packages/bsky/src/api/com/atproto/repo/getRecord.ts index c42c1fd6b4c..95f2c5eda81 100644 --- a/packages/bsky/src/api/com/atproto/repo/getRecord.ts +++ b/packages/bsky/src/api/com/atproto/repo/getRecord.ts @@ -2,37 +2,28 @@ import { InvalidRequestError } from '@atproto/xrpc-server' import { AtUri } from '@atproto/syntax' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' -import { jsonStringToLex } from '@atproto/lexicon' export default function (server: Server, ctx: AppContext) { server.com.atproto.repo.getRecord(async ({ params }) => { const { repo, collection, rkey, cid } = params - const db = ctx.db.getReplica() - const did = await ctx.services.actor(db).getActorDid(repo) + const [did] = await ctx.hydrator.actor.getDids([repo]) if (!did) { throw new InvalidRequestError(`Could not find repo: ${repo}`) } - const uri = AtUri.make(did, collection, rkey) + const uri = AtUri.make(did, collection, rkey).toString() + const result = await ctx.hydrator.getRecord(uri, true) - let builder = db.db - .selectFrom('record') - .selectAll() - .where('uri', '=', uri.toString()) - if (cid) { - builder = builder.where('cid', '=', cid) - } - - const record = await builder.executeTakeFirst() - if (!record) { + if (!result || (cid && result.cid !== cid)) { throw new InvalidRequestError(`Could not locate record: ${uri}`) } + return { - encoding: 'application/json', + encoding: 'application/json' as const, body: { - uri: record.uri, - cid: record.cid, - value: jsonStringToLex(record.json) as Record, + uri: uri, + cid: result.cid, + value: result.record, }, } }) diff --git a/packages/bsky/src/api/com/atproto/temp/fetchLabels.ts b/packages/bsky/src/api/com/atproto/temp/fetchLabels.ts index 8a6cacc2fbd..044a8d4dfd4 100644 --- a/packages/bsky/src/api/com/atproto/temp/fetchLabels.ts +++ b/packages/bsky/src/api/com/atproto/temp/fetchLabels.ts @@ -1,30 +1,9 @@ import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { InvalidRequestError } from '@atproto/xrpc-server' -export default function (server: Server, ctx: AppContext) { - server.com.atproto.temp.fetchLabels(async ({ params }) => { - const { limit } = params - const db = ctx.db.getReplica() - const since = - params.since !== undefined ? new Date(params.since).toISOString() : '' - const labelRes = await db.db - .selectFrom('label') - .selectAll() - .orderBy('label.cts', 'asc') - .where('cts', '>', since) - .limit(limit) - .execute() - - const labels = labelRes.map((l) => ({ - ...l, - cid: l.cid === '' ? undefined : l.cid, - })) - - return { - encoding: 'application/json', - body: { - labels, - }, - } +export default function (server: Server, _ctx: AppContext) { + server.com.atproto.temp.fetchLabels(async (_reqCtx) => { + throw new InvalidRequestError('not implemented on dataplane') }) } diff --git a/packages/bsky/src/api/health.ts b/packages/bsky/src/api/health.ts index b8ebadd4b71..20190cd030c 100644 --- a/packages/bsky/src/api/health.ts +++ b/packages/bsky/src/api/health.ts @@ -1,5 +1,4 @@ import express from 'express' -import { sql } from 'kysely' import AppContext from '../context' export const createRouter = (ctx: AppContext): express.Router => { @@ -21,9 +20,8 @@ export const createRouter = (ctx: AppContext): express.Router => { router.get('/xrpc/_health', async function (req, res) { const { version } = ctx.cfg - const db = ctx.db.getPrimary() try { - await sql`select 1`.execute(db.db) + await ctx.dataplane.ping({}) } catch (err) { req.log.error(err, 'failed health check') return res.status(503).send({ version, error: 'Service Unavailable' }) diff --git a/packages/bsky/src/api/util.ts b/packages/bsky/src/api/util.ts index ef7e51bc95e..2fe54a8a7be 100644 --- a/packages/bsky/src/api/util.ts +++ b/packages/bsky/src/api/util.ts @@ -5,3 +5,8 @@ export const setRepoRev = (res: express.Response, rev: string | null) => { res.setHeader('Atproto-Repo-Rev', rev) } } + +export const clearlyBadCursor = (cursor?: string) => { + // hallmark of v1 cursor, highly unlikely in v2 cursors based on time or rkeys + return !!cursor?.includes('::') +} diff --git a/packages/bsky/src/auth-verifier.ts b/packages/bsky/src/auth-verifier.ts index 5a2bf753072..7798efa99b2 100644 --- a/packages/bsky/src/auth-verifier.ts +++ b/packages/bsky/src/auth-verifier.ts @@ -2,9 +2,16 @@ import { AuthRequiredError, verifyJwt as verifyServiceJwt, } from '@atproto/xrpc-server' -import { IdResolver } from '@atproto/identity' import * as ui8 from 'uint8arrays' import express from 'express' +import { + Code, + DataPlaneClient, + getKeyAsDidKey, + isDataplaneError, + unpackIdentityKeys, +} from './data-plane' +import { GetIdentityByDidResponse } from './proto/bsky_pb' type ReqCtx = { req: express.Request @@ -35,8 +42,6 @@ type RoleOutput = { credentials: { type: 'role' admin: boolean - moderator: boolean - triage: boolean } } @@ -51,29 +56,35 @@ type AdminServiceOutput = { export type AuthVerifierOpts = { ownDid: string adminDid: string - adminPass: string - moderatorPass: string - triagePass: string + adminPasses: string[] } export class AuthVerifier { - private _adminPass: string - private _moderatorPass: string - private _triagePass: string public ownDid: string public adminDid: string + private adminPasses: Set - constructor(public idResolver: IdResolver, opts: AuthVerifierOpts) { - this._adminPass = opts.adminPass - this._moderatorPass = opts.moderatorPass - this._triagePass = opts.triagePass + constructor(public dataplane: DataPlaneClient, opts: AuthVerifierOpts) { this.ownDid = opts.ownDid this.adminDid = opts.adminDid + this.adminPasses = new Set(opts.adminPasses) } // verifiers (arrow fns to preserve scope) standard = async (ctx: ReqCtx): Promise => { + // @TODO remove! basic auth + did supported just for testing. + if (isBasicToken(ctx.req)) { + const aud = this.ownDid + const iss = ctx.req.headers['appview-as-did'] + if (typeof iss !== 'string' || !iss.startsWith('did:')) { + throw new AuthRequiredError('bad issuer') + } + if (!this.parseRoleCreds(ctx.req).admin) { + throw new AuthRequiredError('bad credentials') + } + return { credentials: { type: 'standard', iss, aud } } + } const { iss, aud } = await this.verifyServiceJwt(ctx, { aud: this.ownDid, iss: null, @@ -84,7 +95,7 @@ export class AuthVerifier { standardOptional = async ( ctx: ReqCtx, ): Promise => { - if (isBearerToken(ctx.req)) { + if (isBearerToken(ctx.req) || isBasicToken(ctx.req)) { return this.standard(ctx) } return this.nullCreds() @@ -173,16 +184,10 @@ export class AuthVerifier { return { status: Missing, admin: false, moderator: false, triage: false } } const { username, password } = parsed - if (username === 'admin' && password === this._adminPass) { - return { status: Valid, admin: true, moderator: true, triage: true } - } - if (username === 'admin' && password === this._moderatorPass) { - return { status: Valid, admin: false, moderator: true, triage: true } + if (username === 'admin' && this.adminPasses.has(password)) { + return { status: Valid, admin: true } } - if (username === 'admin' && password === this._triagePass) { - return { status: Valid, admin: false, moderator: false, triage: true } - } - return { status: Invalid, admin: false, moderator: false, triage: false } + return { status: Invalid, admin: false } } async verifyServiceJwt( @@ -191,12 +196,26 @@ export class AuthVerifier { ) { const getSigningKey = async ( did: string, - forceRefresh: boolean, + _forceRefresh: boolean, // @TODO consider propagating to dataplane ): Promise => { if (opts.iss !== null && !opts.iss.includes(did)) { throw new AuthRequiredError('Untrusted issuer', 'UntrustedIss') } - return this.idResolver.did.resolveAtprotoKey(did, forceRefresh) + let identity: GetIdentityByDidResponse + try { + identity = await this.dataplane.getIdentityByDid({ did }) + } catch (err) { + if (isDataplaneError(err, Code.NotFound)) { + throw new AuthRequiredError('identity unknown') + } + throw err + } + const keys = unpackIdentityKeys(identity.keys) + const didKey = getKeyAsDidKey(keys, { id: 'atproto' }) + if (!didKey) { + throw new AuthRequiredError('missing or bad key') + } + return didKey } const jwtStr = bearerTokenFromReq(reqCtx.req) @@ -222,10 +241,10 @@ export class AuthVerifier { const viewer = creds.credentials.type === 'standard' ? creds.credentials.iss : null const canViewTakedowns = - (creds.credentials.type === 'role' && creds.credentials.triage) || + (creds.credentials.type === 'role' && creds.credentials.admin) || creds.credentials.type === 'admin_service' const canPerformTakedown = - (creds.credentials.type === 'role' && creds.credentials.moderator) || + (creds.credentials.type === 'role' && creds.credentials.admin) || creds.credentials.type === 'admin_service' return { viewer, @@ -245,6 +264,10 @@ const isBearerToken = (req: express.Request): boolean => { return req.headers.authorization?.startsWith(BEARER) ?? false } +const isBasicToken = (req: express.Request): boolean => { + return req.headers.authorization?.startsWith(BASIC) ?? false +} + const bearerTokenFromReq = (req: express.Request) => { const header = req.headers.authorization || '' if (!header.startsWith(BEARER)) return null diff --git a/packages/bsky/src/auto-moderator/hive.ts b/packages/bsky/src/auto-moderator/hive.ts deleted file mode 100644 index 51d67c1c783..00000000000 --- a/packages/bsky/src/auto-moderator/hive.ts +++ /dev/null @@ -1,187 +0,0 @@ -import axios from 'axios' -import FormData from 'form-data' -import { CID } from 'multiformats/cid' -import { IdResolver } from '@atproto/identity' -import { PrimaryDatabase } from '../db' -import { retryHttp } from '../util/retry' -import { resolveBlob } from '../api/blob-resolver' -import { labelerLogger as log } from '../logger' - -const HIVE_ENDPOINT = 'https://api.thehive.ai/api/v2/task/sync' - -export interface ImgLabeler { - labelImg(did: string, cid: CID): Promise -} - -export class HiveLabeler implements ImgLabeler { - constructor( - public hiveApiKey: string, - protected ctx: { - db: PrimaryDatabase - idResolver: IdResolver - }, - ) {} - - async labelImg(did: string, cid: CID): Promise { - const hiveRes = await retryHttp(async () => { - try { - return await this.makeHiveReq(did, cid) - } catch (err) { - log.warn({ err, did, cid: cid.toString() }, 'hive request failed') - throw err - } - }) - log.info({ hiveRes, did, cid: cid.toString() }, 'hive response') - const classes = respToClasses(hiveRes) - return summarizeLabels(classes) - } - - async makeHiveReq(did: string, cid: CID): Promise { - const { stream } = await resolveBlob( - did, - cid, - this.ctx.db, - this.ctx.idResolver, - ) - const form = new FormData() - form.append('media', stream) - const { data } = await axios.post(HIVE_ENDPOINT, form, { - headers: { - 'Content-Type': 'multipart/form-data', - authorization: `token ${this.hiveApiKey}`, - accept: 'application/json', - }, - }) - return data - } -} - -export const respToClasses = (res: HiveResp): HiveRespClass[] => { - const classes: HiveRespClass[] = [] - for (const status of res.status) { - for (const out of status.response.output) { - for (const cls of out.classes) { - classes.push(cls) - } - } - } - return classes -} - -// Matches only one (or none) of: porn, sexual, nudity -// -// porn: sexual and nudity. including both explicit activity or full-frontal and suggestive/intent -// sexual: sexually suggestive, not explicit; may include some forms of nudity -// nudity: non-sexual nudity (eg, artistic, possibly some photographic) -// -// hive docs/definitions: https://docs.thehive.ai/docs/sexual-content -export const sexualLabels = (classes: HiveRespClass[]): string[] => { - const scores = {} - - for (const cls of classes) { - scores[cls.class] = cls.score - } - - // first check if porn... - for (const pornClass of [ - 'yes_sexual_activity', - 'animal_genitalia_and_human', - 'yes_realistic_nsfw', - ]) { - if (scores[pornClass] >= 0.9) { - return ['porn'] - } - } - if (scores['general_nsfw'] >= 0.9) { - // special case for some anime examples - if (scores['animated_animal_genitalia'] >= 0.5) { - return ['porn'] - } - // special case for some pornographic/explicit classic drawings - if (scores['yes_undressed'] >= 0.9 && scores['yes_sexual_activity'] > 0.9) { - return ['porn'] - } - } - - // then check for sexual suggestive (which may include nudity)... - for (const sexualClass of ['yes_sexual_intent', 'yes_sex_toy']) { - if (scores[sexualClass] >= 0.9) { - return ['sexual'] - } - } - if (scores['yes_undressed'] >= 0.9) { - // special case for bondage examples - if (scores['yes_sex_toy'] > 0.75) { - return ['sexual'] - } - } - - // then non-sexual nudity... - for (const nudityClass of [ - 'yes_male_nudity', - 'yes_female_nudity', - 'yes_undressed', - ]) { - if (scores[nudityClass] >= 0.9) { - return ['nudity'] - } - } - - // then finally flag remaining "underwear" images in to sexually suggestive - // (after non-sexual content already labeled above) - for (const nudityClass of ['yes_male_underwear', 'yes_female_underwear']) { - if (scores[nudityClass] >= 0.9) { - // TODO: retaining 'underwear' label for a short time to help understand - // the impact of labeling all "underwear" as "sexual". This *will* be - // pulling in somewhat non-sexual content in to "sexual" label. - return ['sexual'] - } - } - - return [] -} - -// gore and violence: https://docs.thehive.ai/docs/class-descriptions-violence-gore -const labelForClass = { - very_bloody: 'gore', - human_corpse: 'corpse', - hanging: 'corpse', -} -const labelForClassLessSensitive = { - yes_self_harm: 'self-harm', -} - -export const summarizeLabels = (classes: HiveRespClass[]): string[] => { - const labels: string[] = sexualLabels(classes) - for (const cls of classes) { - if (labelForClass[cls.class] && cls.score >= 0.9) { - labels.push(labelForClass[cls.class]) - } - } - for (const cls of classes) { - if (labelForClassLessSensitive[cls.class] && cls.score >= 0.96) { - labels.push(labelForClassLessSensitive[cls.class]) - } - } - return labels -} - -type HiveResp = { - status: HiveRespStatus[] -} - -type HiveRespStatus = { - response: { - output: HiveRespOutput[] - } -} - -type HiveRespOutput = { - time: number - classes: HiveRespClass[] -} - -type HiveRespClass = { - class: string - score: number -} diff --git a/packages/bsky/src/auto-moderator/index.ts b/packages/bsky/src/auto-moderator/index.ts deleted file mode 100644 index 7883a26d9e4..00000000000 --- a/packages/bsky/src/auto-moderator/index.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { AtUri } from '@atproto/syntax' -import { AtpAgent } from '@atproto/api' -import { dedupe, getFieldsFromRecord } from './util' -import { labelerLogger as log } from '../logger' -import { PrimaryDatabase } from '../db' -import { IdResolver } from '@atproto/identity' -import { BackgroundQueue } from '../background' -import { IndexerConfig } from '../indexer/config' -import { buildBasicAuth } from '../auth-verifier' -import { CID } from 'multiformats/cid' -import { HiveLabeler, ImgLabeler } from './hive' -import { KeywordLabeler, TextLabeler } from './keyword' -import { ids } from '../lexicon/lexicons' - -export class AutoModerator { - public pushAgent: AtpAgent - public imgLabeler?: ImgLabeler - public textLabeler?: TextLabeler - - constructor( - public ctx: { - db: PrimaryDatabase - idResolver: IdResolver - cfg: IndexerConfig - backgroundQueue: BackgroundQueue - }, - ) { - const { hiveApiKey } = ctx.cfg - this.imgLabeler = hiveApiKey ? new HiveLabeler(hiveApiKey, ctx) : undefined - this.textLabeler = new KeywordLabeler(ctx.cfg.labelerKeywords) - - const url = new URL(ctx.cfg.moderationPushUrl) - this.pushAgent = new AtpAgent({ service: url.origin }) - this.pushAgent.api.setHeader( - 'authorization', - buildBasicAuth(url.username, url.password), - ) - } - - processRecord(uri: AtUri, cid: CID, obj: unknown) { - this.ctx.backgroundQueue.add(async () => { - const { text, imgs } = getFieldsFromRecord(obj, uri) - await this.labelRecord(uri, cid, text, imgs).catch((err) => { - log.error( - { err, uri: uri.toString(), record: obj }, - 'failed to label record', - ) - }) - }) - } - - processHandle(_handle: string, _did: string) { - // no-op since this functionality moved to auto-mod service - } - - async labelRecord(uri: AtUri, recordCid: CID, text: string[], imgs: CID[]) { - if (uri.collection !== ids.AppBskyFeedPost) { - // @TODO label profiles - return - } - const allLabels = await Promise.all([ - this.textLabeler?.labelText(text.join(' ')), - ...imgs.map((cid) => this.imgLabeler?.labelImg(uri.host, cid)), - ]) - const labels = dedupe(allLabels.flat()) - await this.pushLabels(uri, recordCid, labels) - } - - async pushLabels(uri: AtUri, cid: CID, labels: string[]): Promise { - if (labels.length < 1) return - - await this.pushAgent.com.atproto.admin.emitModerationEvent({ - event: { - $type: 'com.atproto.admin.defs#modEventLabel', - comment: '[AutoModerator]: Applying labels', - createLabelVals: labels, - negateLabelVals: [], - }, - subject: { - $type: 'com.atproto.repo.strongRef', - uri: uri.toString(), - cid: cid.toString(), - }, - createdBy: this.ctx.cfg.serverDid, - }) - } - - async processAll() { - await this.ctx.backgroundQueue.processAll() - } -} diff --git a/packages/bsky/src/auto-moderator/keyword.ts b/packages/bsky/src/auto-moderator/keyword.ts deleted file mode 100644 index 6bc504aa142..00000000000 --- a/packages/bsky/src/auto-moderator/keyword.ts +++ /dev/null @@ -1,25 +0,0 @@ -export interface TextLabeler { - labelText(text: string): Promise -} - -export class KeywordLabeler implements TextLabeler { - constructor(public keywords: Record) {} - - async labelText(text: string): Promise { - return keywordLabeling(this.keywords, text) - } -} - -export const keywordLabeling = ( - keywords: Record, - text: string, -): string[] => { - const lowerText = text.toLowerCase() - const labels: string[] = [] - for (const word of Object.keys(keywords)) { - if (lowerText.includes(word)) { - labels.push(keywords[word]) - } - } - return labels -} diff --git a/packages/bsky/src/auto-moderator/util.ts b/packages/bsky/src/auto-moderator/util.ts deleted file mode 100644 index ab1467a07f2..00000000000 --- a/packages/bsky/src/auto-moderator/util.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { CID } from 'multiformats/cid' -import { AtUri } from '@atproto/syntax' -import * as lex from '../lexicon/lexicons' -import { - isRecord as isPost, - Record as PostRecord, -} from '../lexicon/types/app/bsky/feed/post' -import { - isRecord as isProfile, - Record as ProfileRecord, -} from '../lexicon/types/app/bsky/actor/profile' -import { - isRecord as isList, - Record as ListRecord, -} from '../lexicon/types/app/bsky/graph/list' -import { - isRecord as isGenerator, - Record as GeneratorRecord, -} from '../lexicon/types/app/bsky/feed/generator' -import { isMain as isEmbedImage } from '../lexicon/types/app/bsky/embed/images' -import { isMain as isEmbedExternal } from '../lexicon/types/app/bsky/embed/external' -import { isMain as isEmbedRecordWithMedia } from '../lexicon/types/app/bsky/embed/recordWithMedia' - -type RecordFields = { - text: string[] - imgs: CID[] -} - -export const getFieldsFromRecord = ( - record: unknown, - uri: AtUri, -): RecordFields => { - if (isPost(record)) { - return getFieldsFromPost(record) - } else if (isProfile(record)) { - return getFieldsFromProfile(record) - } else if (isList(record)) { - return getFieldsFromList(record) - } else if (isGenerator(record)) { - return getFieldsFromGenerator(record, uri) - } else { - return { text: [], imgs: [] } - } -} - -export const getFieldsFromPost = (record: PostRecord): RecordFields => { - const text: string[] = [] - const imgs: CID[] = [] - text.push(record.text) - const embeds = separateEmbeds(record.embed) - for (const embed of embeds) { - if (isEmbedImage(embed)) { - for (const img of embed.images) { - imgs.push(img.image.ref) - text.push(img.alt) - } - } else if (isEmbedExternal(embed)) { - if (embed.external.thumb) { - imgs.push(embed.external.thumb.ref) - } - text.push(embed.external.title) - text.push(embed.external.description) - } - } - return { text, imgs } -} - -export const getFieldsFromProfile = (record: ProfileRecord): RecordFields => { - const text: string[] = [] - const imgs: CID[] = [] - if (record.displayName) { - text.push(record.displayName) - } - if (record.description) { - text.push(record.description) - } - if (record.avatar) { - imgs.push(record.avatar.ref) - } - if (record.banner) { - imgs.push(record.banner.ref) - } - return { text, imgs } -} - -export const getFieldsFromList = (record: ListRecord): RecordFields => { - const text: string[] = [] - const imgs: CID[] = [] - if (record.name) { - text.push(record.name) - } - if (record.description) { - text.push(record.description) - } - if (record.avatar) { - imgs.push(record.avatar.ref) - } - return { text, imgs } -} - -export const getFieldsFromGenerator = ( - record: GeneratorRecord, - uri: AtUri, -): RecordFields => { - const text: string[] = [] - const imgs: CID[] = [] - text.push(uri.rkey) - if (record.displayName) { - text.push(record.displayName) - } - if (record.description) { - text.push(record.description) - } - if (record.avatar) { - imgs.push(record.avatar.ref) - } - return { text, imgs } -} - -export const dedupe = (strs: (string | undefined)[]): string[] => { - const set = new Set() - for (const str of strs) { - if (str !== undefined) { - set.add(str) - } - } - return [...set] -} - -const separateEmbeds = (embed: PostRecord['embed']) => { - if (!embed) { - return [] - } - if (isEmbedRecordWithMedia(embed)) { - return [{ $type: lex.ids.AppBskyEmbedRecord, ...embed.record }, embed.media] - } - return [embed] -} diff --git a/packages/bsky/src/config.ts b/packages/bsky/src/config.ts index 1d88abb588a..6f9c96776f4 100644 --- a/packages/bsky/src/config.ts +++ b/packages/bsky/src/config.ts @@ -1,53 +1,35 @@ -import assert from 'assert' -import { - DAY, - HOUR, - MINUTE, - SECOND, - parseIntWithFallback, -} from '@atproto/common' +import assert from 'node:assert' export interface ServerConfigValues { - version: string + // service + version?: string debugMode?: boolean port?: number publicUrl?: string serverDid: string - feedGenDid?: string - dbPrimaryPostgresUrl: string - dbReplicaPostgresUrls?: string[] - dbReplicaTags?: Record // E.g. { timeline: [0], thread: [1] } - dbPostgresSchema?: string - redisHost?: string // either set redis host, or both sentinel name and hosts - redisSentinelName?: string - redisSentinelHosts?: string[] - redisPassword?: string - didPlcUrl: string - didCacheStaleTTL: number - didCacheMaxTTL: number - labelCacheStaleTTL: number - labelCacheMaxTTL: number - handleResolveNameservers?: string[] - imgUriEndpoint?: string - blobCacheLocation?: string - searchEndpoint?: string - bsyncUrl?: string + // external services + dataplaneUrls: string[] + dataplaneHttpVersion?: '1.1' | '2' + dataplaneIgnoreBadTls?: boolean + bsyncUrl: string bsyncApiKey?: string bsyncHttpVersion?: '1.1' | '2' bsyncIgnoreBadTls?: boolean - bsyncOnlyMutes?: boolean - courierUrl?: string + courierUrl: string courierApiKey?: string courierHttpVersion?: '1.1' | '2' courierIgnoreBadTls?: boolean - courierOnlyRegistration?: boolean - adminPassword: string - moderatorPassword: string - triagePassword: string + searchUrl?: string + cdnUrl?: string + // identity + didPlcUrl: string + handleResolveNameservers?: string[] + // moderation and administration modServiceDid: string - rateLimitsEnabled: boolean - rateLimitBypassKey?: string - rateLimitBypassIps?: string[] + adminPasswords: string[] + labelsFromIssuerDids?: string[] + // misc/dev + blobCacheLocation?: string } export class ServerConfig { @@ -55,149 +37,77 @@ export class ServerConfig { constructor(private cfg: ServerConfigValues) {} static readEnv(overrides?: Partial) { - const version = process.env.BSKY_VERSION || '0.0.0' + const version = process.env.BSKY_VERSION || undefined const debugMode = process.env.NODE_ENV !== 'production' - const publicUrl = process.env.PUBLIC_URL || undefined - const serverDid = process.env.SERVER_DID || 'did:example:test' - const feedGenDid = process.env.FEED_GEN_DID - const envPort = parseInt(process.env.PORT || '', 10) + const publicUrl = process.env.BSKY_PUBLIC_URL || undefined + const serverDid = process.env.BSKY_SERVER_DID || 'did:example:test' + const envPort = parseInt(process.env.BSKY_PORT || '', 10) const port = isNaN(envPort) ? 2584 : envPort - const redisHost = - overrides?.redisHost || process.env.REDIS_HOST || undefined - const redisSentinelName = - overrides?.redisSentinelName || - process.env.REDIS_SENTINEL_NAME || + const didPlcUrl = process.env.BSKY_DID_PLC_URL || 'http://localhost:2582' + const handleResolveNameservers = process.env.BSKY_HANDLE_RESOLVE_NAMESERVERS + ? process.env.BSKY_HANDLE_RESOLVE_NAMESERVERS.split(',') + : [] + const cdnUrl = process.env.BSKY_CDN_URL || process.env.BSKY_IMG_URI_ENDPOINT + const blobCacheLocation = process.env.BSKY_BLOB_CACHE_LOC + const searchUrl = + process.env.BSKY_SEARCH_URL || + process.env.BSKY_SEARCH_ENDPOINT || undefined - const redisSentinelHosts = - overrides?.redisSentinelHosts || - (process.env.REDIS_SENTINEL_HOSTS - ? process.env.REDIS_SENTINEL_HOSTS.split(',') - : []) - const redisPassword = - overrides?.redisPassword || process.env.REDIS_PASSWORD || undefined - const didPlcUrl = process.env.DID_PLC_URL || 'http://localhost:2582' - const didCacheStaleTTL = parseIntWithFallback( - process.env.DID_CACHE_STALE_TTL, - HOUR, - ) - const didCacheMaxTTL = parseIntWithFallback( - process.env.DID_CACHE_MAX_TTL, - DAY, - ) - const labelCacheStaleTTL = parseIntWithFallback( - process.env.LABEL_CACHE_STALE_TTL, - 30 * SECOND, - ) - const labelCacheMaxTTL = parseIntWithFallback( - process.env.LABEL_CACHE_MAX_TTL, - MINUTE, - ) - const handleResolveNameservers = process.env.HANDLE_RESOLVE_NAMESERVERS - ? process.env.HANDLE_RESOLVE_NAMESERVERS.split(',') + let dataplaneUrls = overrides?.dataplaneUrls + dataplaneUrls ??= process.env.BSKY_DATAPLANE_URLS + ? process.env.BSKY_DATAPLANE_URLS.split(',') + : [] + const dataplaneHttpVersion = process.env.BSKY_DATAPLANE_HTTP_VERSION || '2' + const dataplaneIgnoreBadTls = + process.env.BSKY_DATAPLANE_IGNORE_BAD_TLS === 'true' + const labelsFromIssuerDids = process.env.BSKY_LABELS_FROM_ISSUER_DIDS + ? process.env.BSKY_LABELS_FROM_ISSUER_DIDS.split(',') : [] - const imgUriEndpoint = process.env.IMG_URI_ENDPOINT - const blobCacheLocation = process.env.BLOB_CACHE_LOC - const searchEndpoint = process.env.SEARCH_ENDPOINT const bsyncUrl = process.env.BSKY_BSYNC_URL || undefined + assert(bsyncUrl) const bsyncApiKey = process.env.BSKY_BSYNC_API_KEY || undefined const bsyncHttpVersion = process.env.BSKY_BSYNC_HTTP_VERSION || '2' const bsyncIgnoreBadTls = process.env.BSKY_BSYNC_IGNORE_BAD_TLS === 'true' - const bsyncOnlyMutes = process.env.BSKY_BSYNC_ONLY_MUTES === 'true' - assert(!bsyncOnlyMutes || bsyncUrl, 'bsync-only mutes requires a bsync url') assert(bsyncHttpVersion === '1.1' || bsyncHttpVersion === '2') const courierUrl = process.env.BSKY_COURIER_URL || undefined + assert(courierUrl) const courierApiKey = process.env.BSKY_COURIER_API_KEY || undefined const courierHttpVersion = process.env.BSKY_COURIER_HTTP_VERSION || '2' const courierIgnoreBadTls = process.env.BSKY_COURIER_IGNORE_BAD_TLS === 'true' - const courierOnlyRegistration = - process.env.BSKY_COURIER_ONLY_REGISTRATION === 'true' - assert( - !courierOnlyRegistration || courierUrl, - 'courier-only registration requires a courier url', - ) assert(courierHttpVersion === '1.1' || courierHttpVersion === '2') - const dbPrimaryPostgresUrl = - overrides?.dbPrimaryPostgresUrl || process.env.DB_PRIMARY_POSTGRES_URL - let dbReplicaPostgresUrls = overrides?.dbReplicaPostgresUrls - if (!dbReplicaPostgresUrls && process.env.DB_REPLICA_POSTGRES_URLS) { - dbReplicaPostgresUrls = process.env.DB_REPLICA_POSTGRES_URLS.split(',') - } - const dbReplicaTags = overrides?.dbReplicaTags ?? { - '*': getTagIdxs(process.env.DB_REPLICA_TAGS_ANY), // e.g. DB_REPLICA_TAGS_ANY=0,1 - timeline: getTagIdxs(process.env.DB_REPLICA_TAGS_TIMELINE), - feed: getTagIdxs(process.env.DB_REPLICA_TAGS_FEED), - search: getTagIdxs(process.env.DB_REPLICA_TAGS_SEARCH), - thread: getTagIdxs(process.env.DB_REPLICA_TAGS_THREAD), - } - assert( - Object.values(dbReplicaTags) - .flat() - .every((idx) => idx < (dbReplicaPostgresUrls?.length ?? 0)), - 'out of range index in replica tags', + const adminPasswords = envList( + process.env.BSKY_ADMIN_PASSWORDS || process.env.BSKY_ADMIN_PASSWORD, ) - const dbPostgresSchema = process.env.DB_POSTGRES_SCHEMA - assert(dbPrimaryPostgresUrl) - const adminPassword = process.env.ADMIN_PASSWORD || undefined - assert(adminPassword) - const moderatorPassword = process.env.MODERATOR_PASSWORD || undefined - assert(moderatorPassword) - const triagePassword = process.env.TRIAGE_PASSWORD || undefined - assert(triagePassword) - const modServiceDid = - overrides?.modServiceDid || - process.env.MODERATION_SERVICE_DID || - undefined + const modServiceDid = process.env.MOD_SERVICE_DID assert(modServiceDid) - const rateLimitsEnabled = process.env.RATE_LIMITS_ENABLED === 'true' - const rateLimitBypassKey = process.env.RATE_LIMIT_BYPASS_KEY - const rateLimitBypassIps = process.env.RATE_LIMIT_BYPASS_IPS - ? process.env.RATE_LIMIT_BYPASS_IPS.split(',').map((ipOrCidr) => - ipOrCidr.split('/')[0]?.trim(), - ) - : undefined - + assert(dataplaneUrls.length) + assert(dataplaneHttpVersion === '1.1' || dataplaneHttpVersion === '2') return new ServerConfig({ version, debugMode, port, publicUrl, serverDid, - feedGenDid, - dbPrimaryPostgresUrl, - dbReplicaPostgresUrls, - dbReplicaTags, - dbPostgresSchema, - redisHost, - redisSentinelName, - redisSentinelHosts, - redisPassword, + dataplaneUrls, + dataplaneHttpVersion, + dataplaneIgnoreBadTls, + searchUrl, didPlcUrl, - didCacheStaleTTL, - didCacheMaxTTL, - labelCacheStaleTTL, - labelCacheMaxTTL, + labelsFromIssuerDids, handleResolveNameservers, - imgUriEndpoint, + cdnUrl, blobCacheLocation, - searchEndpoint, bsyncUrl, bsyncApiKey, bsyncHttpVersion, bsyncIgnoreBadTls, - bsyncOnlyMutes, courierUrl, courierApiKey, courierHttpVersion, courierIgnoreBadTls, - courierOnlyRegistration, - adminPassword, - moderatorPassword, - triagePassword, + adminPasswords, modServiceDid, - rateLimitsEnabled, - rateLimitBypassKey, - rateLimitBypassIps, ...stripUndefineds(overrides ?? {}), }) } @@ -235,76 +145,16 @@ export class ServerConfig { return this.cfg.serverDid } - get feedGenDid() { - return this.cfg.feedGenDid - } - - get dbPrimaryPostgresUrl() { - return this.cfg.dbPrimaryPostgresUrl - } - - get dbReplicaPostgresUrl() { - return this.cfg.dbReplicaPostgresUrls - } - - get dbReplicaTags() { - return this.cfg.dbReplicaTags - } - - get dbPostgresSchema() { - return this.cfg.dbPostgresSchema + get dataplaneUrls() { + return this.cfg.dataplaneUrls } - get redisHost() { - return this.cfg.redisHost + get dataplaneHttpVersion() { + return this.cfg.dataplaneHttpVersion } - get redisSentinelName() { - return this.cfg.redisSentinelName - } - - get redisSentinelHosts() { - return this.cfg.redisSentinelHosts - } - - get redisPassword() { - return this.cfg.redisPassword - } - - get didCacheStaleTTL() { - return this.cfg.didCacheStaleTTL - } - - get didCacheMaxTTL() { - return this.cfg.didCacheMaxTTL - } - - get labelCacheStaleTTL() { - return this.cfg.labelCacheStaleTTL - } - - get labelCacheMaxTTL() { - return this.cfg.labelCacheMaxTTL - } - - get handleResolveNameservers() { - return this.cfg.handleResolveNameservers - } - - get didPlcUrl() { - return this.cfg.didPlcUrl - } - - get imgUriEndpoint() { - return this.cfg.imgUriEndpoint - } - - get blobCacheLocation() { - return this.cfg.blobCacheLocation - } - - get searchEndpoint() { - return this.cfg.searchEndpoint + get dataplaneIgnoreBadTls() { + return this.cfg.dataplaneIgnoreBadTls } get bsyncUrl() { @@ -315,10 +165,6 @@ export class ServerConfig { return this.cfg.bsyncApiKey } - get bsyncOnlyMutes() { - return this.cfg.bsyncOnlyMutes - } - get bsyncHttpVersion() { return this.cfg.bsyncHttpVersion } @@ -343,43 +189,39 @@ export class ServerConfig { return this.cfg.courierIgnoreBadTls } - get courierOnlyRegistration() { - return this.cfg.courierOnlyRegistration + get searchUrl() { + return this.cfg.searchUrl } - get adminPassword() { - return this.cfg.adminPassword + get cdnUrl() { + return this.cfg.cdnUrl } - get moderatorPassword() { - return this.cfg.moderatorPassword + get didPlcUrl() { + return this.cfg.didPlcUrl } - get triagePassword() { - return this.cfg.triagePassword + get handleResolveNameservers() { + return this.cfg.handleResolveNameservers } - get modServiceDid() { - return this.cfg.modServiceDid + get adminPasswords() { + return this.cfg.adminPasswords } - get rateLimitsEnabled() { - return this.cfg.rateLimitsEnabled + get modServiceDid() { + return this.cfg.modServiceDid } - get rateLimitBypassKey() { - return this.cfg.rateLimitBypassKey + get labelsFromIssuerDids() { + return this.cfg.labelsFromIssuerDids ?? [] } - get rateLimitBypassIps() { - return this.cfg.rateLimitBypassIps + get blobCacheLocation() { + return this.cfg.blobCacheLocation } } -function getTagIdxs(str?: string): number[] { - return str ? str.split(',').map((item) => parseInt(item, 10)) : [] -} - function stripUndefineds( obj: Record, ): Record { @@ -391,3 +233,8 @@ function stripUndefineds( }) return result } + +function envList(str: string | undefined): string[] { + if (str === undefined || str.length === 0) return [] + return str.split(',') +} diff --git a/packages/bsky/src/context.ts b/packages/bsky/src/context.ts index db9779e54b9..3b297caf095 100644 --- a/packages/bsky/src/context.ts +++ b/packages/bsky/src/context.ts @@ -1,15 +1,12 @@ import * as plc from '@did-plc/lib' import { IdResolver } from '@atproto/identity' -import { AtpAgent } from '@atproto/api' +import AtpAgent from '@atproto/api' import { Keypair } from '@atproto/crypto' import { createServiceJwt } from '@atproto/xrpc-server' -import { DatabaseCoordinator } from './db' import { ServerConfig } from './config' -import { ImageUriBuilder } from './image/uri' -import { Services } from './services' -import DidRedisCache from './did-cache' -import { BackgroundQueue } from './background' -import { Redis } from './redis' +import { DataPlaneClient } from './data-plane/client' +import { Hydrator } from './hydration/hydrator' +import { Views } from './views' import { AuthVerifier } from './auth-verifier' import { BsyncClient } from './bsync' import { CourierClient } from './courier' @@ -17,36 +14,37 @@ import { CourierClient } from './courier' export class AppContext { constructor( private opts: { - db: DatabaseCoordinator - imgUriBuilder: ImageUriBuilder cfg: ServerConfig - services: Services + dataplane: DataPlaneClient + searchAgent: AtpAgent | undefined + hydrator: Hydrator + views: Views signingKey: Keypair idResolver: IdResolver - didCache: DidRedisCache - redis: Redis - backgroundQueue: BackgroundQueue - searchAgent?: AtpAgent - bsyncClient?: BsyncClient - courierClient?: CourierClient + bsyncClient: BsyncClient + courierClient: CourierClient authVerifier: AuthVerifier }, ) {} - get db(): DatabaseCoordinator { - return this.opts.db + get cfg(): ServerConfig { + return this.opts.cfg } - get imgUriBuilder(): ImageUriBuilder { - return this.opts.imgUriBuilder + get dataplane(): DataPlaneClient { + return this.opts.dataplane } - get cfg(): ServerConfig { - return this.opts.cfg + get searchAgent(): AtpAgent | undefined { + return this.opts.searchAgent + } + + get hydrator(): Hydrator { + return this.opts.hydrator } - get services(): Services { - return this.opts.services + get views(): Views { + return this.opts.views } get signingKey(): Keypair { @@ -61,23 +59,11 @@ export class AppContext { return this.opts.idResolver } - get didCache(): DidRedisCache { - return this.opts.didCache - } - - get redis(): Redis { - return this.opts.redis - } - - get searchAgent(): AtpAgent | undefined { - return this.opts.searchAgent - } - - get bsyncClient(): BsyncClient | undefined { + get bsyncClient(): BsyncClient { return this.opts.bsyncClient } - get courierClient(): CourierClient | undefined { + get courierClient(): CourierClient { return this.opts.courierClient } @@ -93,10 +79,6 @@ export class AppContext { keypair: this.signingKey, }) } - - get backgroundQueue(): BackgroundQueue { - return this.opts.backgroundQueue - } } export default AppContext diff --git a/packages/bsky/src/daemon/config.ts b/packages/bsky/src/daemon/config.ts deleted file mode 100644 index 3dd7d557652..00000000000 --- a/packages/bsky/src/daemon/config.ts +++ /dev/null @@ -1,60 +0,0 @@ -import assert from 'assert' - -export interface DaemonConfigValues { - version: string - dbPostgresUrl: string - dbPostgresSchema?: string - notificationsDaemonFromDid?: string -} - -export class DaemonConfig { - constructor(private cfg: DaemonConfigValues) {} - - static readEnv(overrides?: Partial) { - const version = process.env.BSKY_VERSION || '0.0.0' - const dbPostgresUrl = - overrides?.dbPostgresUrl || process.env.DB_PRIMARY_POSTGRES_URL - const dbPostgresSchema = - overrides?.dbPostgresSchema || process.env.DB_POSTGRES_SCHEMA - const notificationsDaemonFromDid = - overrides?.notificationsDaemonFromDid || - process.env.BSKY_NOTIFS_DAEMON_FROM_DID || - undefined - assert(dbPostgresUrl) - return new DaemonConfig({ - version, - dbPostgresUrl, - dbPostgresSchema, - notificationsDaemonFromDid, - ...stripUndefineds(overrides ?? {}), - }) - } - - get version() { - return this.cfg.version - } - - get dbPostgresUrl() { - return this.cfg.dbPostgresUrl - } - - get dbPostgresSchema() { - return this.cfg.dbPostgresSchema - } - - get notificationsDaemonFromDid() { - return this.cfg.notificationsDaemonFromDid - } -} - -function stripUndefineds( - obj: Record, -): Record { - const result = {} - Object.entries(obj).forEach(([key, val]) => { - if (val !== undefined) { - result[key] = val - } - }) - return result -} diff --git a/packages/bsky/src/daemon/context.ts b/packages/bsky/src/daemon/context.ts deleted file mode 100644 index dd3d5c1114f..00000000000 --- a/packages/bsky/src/daemon/context.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { PrimaryDatabase } from '../db' -import { DaemonConfig } from './config' -import { Services } from './services' - -export class DaemonContext { - constructor( - private opts: { - db: PrimaryDatabase - cfg: DaemonConfig - services: Services - }, - ) {} - - get db(): PrimaryDatabase { - return this.opts.db - } - - get cfg(): DaemonConfig { - return this.opts.cfg - } - - get services(): Services { - return this.opts.services - } -} - -export default DaemonContext diff --git a/packages/bsky/src/daemon/index.ts b/packages/bsky/src/daemon/index.ts deleted file mode 100644 index 80da01edc2f..00000000000 --- a/packages/bsky/src/daemon/index.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { PrimaryDatabase } from '../db' -import { dbLogger } from '../logger' -import { DaemonConfig } from './config' -import { DaemonContext } from './context' -import { createServices } from './services' -import { ImageUriBuilder } from '../image/uri' -import { NotificationsDaemon } from './notifications' -import logger from './logger' - -export { DaemonConfig } from './config' -export type { DaemonConfigValues } from './config' - -export class BskyDaemon { - public ctx: DaemonContext - public notifications: NotificationsDaemon - private dbStatsInterval: NodeJS.Timer - private notifStatsInterval: NodeJS.Timer - - constructor(opts: { - ctx: DaemonContext - notifications: NotificationsDaemon - }) { - this.ctx = opts.ctx - this.notifications = opts.notifications - } - - static create(opts: { db: PrimaryDatabase; cfg: DaemonConfig }): BskyDaemon { - const { db, cfg } = opts - const imgUriBuilder = new ImageUriBuilder('https://daemon.invalid') // will not be used by daemon - const services = createServices({ - imgUriBuilder, - }) - const ctx = new DaemonContext({ - db, - cfg, - services, - }) - const notifications = new NotificationsDaemon(ctx) - return new BskyDaemon({ ctx, notifications }) - } - - async start() { - const { db, cfg } = this.ctx - const pool = db.pool - this.notifications.run({ - startFromDid: cfg.notificationsDaemonFromDid, - }) - this.dbStatsInterval = setInterval(() => { - dbLogger.info( - { - idleCount: pool.idleCount, - totalCount: pool.totalCount, - waitingCount: pool.waitingCount, - }, - 'db pool stats', - ) - }, 10000) - this.notifStatsInterval = setInterval(() => { - logger.info( - { - count: this.notifications.count, - lastDid: this.notifications.lastDid, - }, - 'notifications daemon stats', - ) - }, 10000) - return this - } - - async destroy(): Promise { - await this.notifications.destroy() - await this.ctx.db.close() - clearInterval(this.dbStatsInterval) - clearInterval(this.notifStatsInterval) - } -} - -export default BskyDaemon diff --git a/packages/bsky/src/daemon/logger.ts b/packages/bsky/src/daemon/logger.ts deleted file mode 100644 index 8599acc315e..00000000000 --- a/packages/bsky/src/daemon/logger.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { subsystemLogger } from '@atproto/common' - -const logger: ReturnType = - subsystemLogger('bsky:daemon') - -export default logger diff --git a/packages/bsky/src/daemon/notifications.ts b/packages/bsky/src/daemon/notifications.ts deleted file mode 100644 index 96431af8c1f..00000000000 --- a/packages/bsky/src/daemon/notifications.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { tidyNotifications } from '../services/util/notification' -import DaemonContext from './context' -import logger from './logger' - -export class NotificationsDaemon { - ac = new AbortController() - running: Promise | undefined - count = 0 - lastDid: string | null = null - - constructor(private ctx: DaemonContext) {} - - run(opts?: RunOptions) { - if (this.running) return - this.count = 0 - this.lastDid = null - this.ac = new AbortController() - this.running = this.tidyNotifications({ - ...opts, - forever: opts?.forever !== false, // run forever by default - }) - .catch((err) => { - // allow this to cause an unhandled rejection, let deployment handle the crash. - logger.error({ err }, 'notifications daemon crashed') - throw err - }) - .finally(() => (this.running = undefined)) - } - - private async tidyNotifications(opts: RunOptions) { - const actorService = this.ctx.services.actor(this.ctx.db) - for await (const { did } of actorService.all(opts)) { - if (this.ac.signal.aborted) return - try { - await tidyNotifications(this.ctx.db, did) - this.count++ - this.lastDid = did - } catch (err) { - logger.warn({ err, did }, 'failed to tidy notifications for actor') - } - } - } - - async destroy() { - this.ac.abort() - await this.running - } -} - -type RunOptions = { - forever?: boolean - batchSize?: number - startFromDid?: string -} diff --git a/packages/bsky/src/daemon/services.ts b/packages/bsky/src/daemon/services.ts deleted file mode 100644 index 93141d13a08..00000000000 --- a/packages/bsky/src/daemon/services.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { PrimaryDatabase } from '../db' -import { ActorService } from '../services/actor' -import { ImageUriBuilder } from '../image/uri' -import { GraphService } from '../services/graph' -import { LabelService } from '../services/label' - -export function createServices(resources: { - imgUriBuilder: ImageUriBuilder -}): Services { - const { imgUriBuilder } = resources - const graph = GraphService.creator(imgUriBuilder) - const label = LabelService.creator(null) - return { - actor: ActorService.creator(imgUriBuilder, graph, label), - } -} - -export type Services = { - actor: FromDbPrimary -} - -type FromDbPrimary = (db: PrimaryDatabase) => T diff --git a/packages/bsky/src/data-plane/bsync/index.ts b/packages/bsky/src/data-plane/bsync/index.ts new file mode 100644 index 00000000000..a1c82bc1126 --- /dev/null +++ b/packages/bsky/src/data-plane/bsync/index.ts @@ -0,0 +1,98 @@ +import http from 'http' +import events from 'events' +import express from 'express' +import { ConnectRouter } from '@connectrpc/connect' +import { expressConnectMiddleware } from '@connectrpc/connect-express' +import { Database } from '../server/db' +import { Service } from '../../proto/bsync_connect' +import { MuteOperation_Type } from '../../proto/bsync_pb' +import assert from 'assert' + +export class MockBsync { + constructor(public server: http.Server) {} + + static async create(db: Database, port: number) { + const app = express() + const routes = createRoutes(db) + app.use(expressConnectMiddleware({ routes })) + const server = app.listen(port) + await events.once(server, 'listening') + return new MockBsync(server) + } + + async destroy() { + return new Promise((resolve, reject) => { + this.server.close((err) => { + if (err) { + reject(err) + } else { + resolve() + } + }) + }) + } +} + +const createRoutes = (db: Database) => (router: ConnectRouter) => + router.service(Service, { + async addMuteOperation(req) { + const { type, actorDid, subject } = req + if (type === MuteOperation_Type.ADD) { + if (subject.startsWith('did:')) { + assert(actorDid !== subject, 'cannot mute yourself') // @TODO pass message through in http error + await db.db + .insertInto('mute') + .values({ + mutedByDid: actorDid, + subjectDid: subject, + createdAt: new Date().toISOString(), + }) + .onConflict((oc) => oc.doNothing()) + .execute() + } else { + await db.db + .insertInto('list_mute') + .values({ + mutedByDid: actorDid, + listUri: subject, + createdAt: new Date().toISOString(), + }) + .onConflict((oc) => oc.doNothing()) + .execute() + } + } else if (type === MuteOperation_Type.REMOVE) { + if (subject.startsWith('did:')) { + await db.db + .deleteFrom('mute') + .where('mutedByDid', '=', actorDid) + .where('subjectDid', '=', subject) + .execute() + } else { + await db.db + .deleteFrom('list_mute') + .where('mutedByDid', '=', actorDid) + .where('listUri', '=', subject) + .execute() + } + } else if (type === MuteOperation_Type.CLEAR) { + await db.db + .deleteFrom('mute') + .where('mutedByDid', '=', actorDid) + .execute() + await db.db + .deleteFrom('list_mute') + .where('mutedByDid', '=', actorDid) + .execute() + } + + return {} + }, + + async scanMuteOperations() { + throw new Error('not implemented') + }, + + async ping() { + return {} + }, + }) diff --git a/packages/bsky/src/data-plane/client.ts b/packages/bsky/src/data-plane/client.ts new file mode 100644 index 00000000000..dd525267fe3 --- /dev/null +++ b/packages/bsky/src/data-plane/client.ts @@ -0,0 +1,151 @@ +import assert from 'node:assert' +import { randomInt } from 'node:crypto' +import * as ui8 from 'uint8arrays' +import { + Code, + ConnectError, + PromiseClient, + createPromiseClient, + makeAnyClient, +} from '@connectrpc/connect' +import { createGrpcTransport } from '@connectrpc/connect-node' +import { getDidKeyFromMultibase } from '@atproto/identity' +import { Service } from '../proto/bsky_connect' + +export type DataPlaneClient = PromiseClient +type BaseClient = { lib: DataPlaneClient; url: URL } +type HttpVersion = '1.1' | '2' +const MAX_RETRIES = 3 + +export const createDataPlaneClient = ( + baseUrls: string[], + opts: { httpVersion?: HttpVersion; rejectUnauthorized?: boolean }, +) => { + const clients = baseUrls.map((baseUrl) => createBaseClient(baseUrl, opts)) + assert(clients.length > 0, 'no clients available') + return makeAnyClient(Service, (method) => { + return async (...args) => { + let tries = 0 + let error: unknown + let remainingClients = clients + while (tries < MAX_RETRIES) { + const client = randomElement(remainingClients) + assert(client, 'no clients available') + try { + return await client.lib[method.localName](...args) + } catch (err) { + if (err instanceof ConnectError && err.code === Code.Unavailable) { + tries++ + error = err + remainingClients = getRemainingClients(remainingClients, client) + } else { + throw err + } + } + } + assert(error) + throw error + } + }) as DataPlaneClient +} + +export { Code } + +export const isDataplaneError = ( + err: unknown, + code?: Code, +): err is ConnectError => { + if (err instanceof ConnectError) { + return !code || err.code === code + } + return false +} + +const createBaseClient = ( + baseUrl: string, + opts: { httpVersion?: HttpVersion; rejectUnauthorized?: boolean }, +): BaseClient => { + const { httpVersion = '2', rejectUnauthorized = true } = opts + const transport = createGrpcTransport({ + baseUrl, + httpVersion, + acceptCompression: [], + nodeOptions: { rejectUnauthorized }, + }) + return { + lib: createPromiseClient(Service, transport), + url: new URL(baseUrl), + } +} + +const getRemainingClients = (clients: BaseClient[], lastClient: BaseClient) => { + if (clients.length < 2) return clients // no clients to choose from + if (lastClient.url.port) { + // if the last client had a port, we attempt to exclude its whole host. + const maybeRemaining = clients.filter( + (c) => c.url.hostname !== lastClient.url.hostname, + ) + if (maybeRemaining.length) { + return maybeRemaining + } + } + return clients.filter((c) => c !== lastClient) +} + +const randomElement = (arr: T[]): T | undefined => { + if (arr.length === 0) return + return arr[randomInt(arr.length)] +} + +export const unpackIdentityServices = (servicesBytes: Uint8Array) => { + const servicesStr = ui8.toString(servicesBytes, 'utf8') + if (!servicesStr) return {} + return JSON.parse(servicesStr) as UnpackedServices +} + +export const unpackIdentityKeys = (keysBytes: Uint8Array) => { + const keysStr = ui8.toString(keysBytes, 'utf8') + if (!keysStr) return {} + return JSON.parse(keysStr) as UnpackedKeys +} + +export const getServiceEndpoint = ( + services: UnpackedServices, + opts: { id: string; type: string }, +) => { + const endpoint = + services[opts.id] && + services[opts.id].Type === opts.type && + validateUrl(services[opts.id].URL) + return endpoint || undefined +} + +export const getKeyAsDidKey = (keys: UnpackedKeys, opts: { id: string }) => { + const key = + keys[opts.id] && + getDidKeyFromMultibase({ + type: keys[opts.id].Type, + publicKeyMultibase: keys[opts.id].PublicKeyMultibase, + }) + return key || undefined +} + +type UnpackedServices = Record + +type UnpackedKeys = Record + +const validateUrl = (urlStr: string): string | undefined => { + let url + try { + url = new URL(urlStr) + } catch { + return undefined + } + if (!['http:', 'https:'].includes(url.protocol)) { + return undefined + } else if (!url.hostname) { + return undefined + } else { + return urlStr + } +} diff --git a/packages/bsky/src/data-plane/index.ts b/packages/bsky/src/data-plane/index.ts new file mode 100644 index 00000000000..31fe4b0dde3 --- /dev/null +++ b/packages/bsky/src/data-plane/index.ts @@ -0,0 +1,3 @@ +export * from './server' +export * from './client' +export * from './bsync' diff --git a/packages/bsky/src/background.ts b/packages/bsky/src/data-plane/server/background.ts similarity index 76% rename from packages/bsky/src/background.ts rename to packages/bsky/src/data-plane/server/background.ts index 466bad80a51..59d8ccf0ddf 100644 --- a/packages/bsky/src/background.ts +++ b/packages/bsky/src/data-plane/server/background.ts @@ -1,13 +1,13 @@ import PQueue from 'p-queue' -import { PrimaryDatabase } from './db' -import { dbLogger } from './logger' +import { Database } from './db' +import { dbLogger } from '../../logger' // A simple queue for in-process, out-of-band/backgrounded work export class BackgroundQueue { - queue = new PQueue({ concurrency: 20 }) + queue = new PQueue() destroyed = false - constructor(public db: PrimaryDatabase) {} + constructor(public db: Database) {} add(task: Task) { if (this.destroyed) { @@ -32,4 +32,4 @@ export class BackgroundQueue { } } -type Task = (db: PrimaryDatabase) => Promise +type Task = (db: Database) => Promise diff --git a/packages/bsky/src/db/database-schema.ts b/packages/bsky/src/data-plane/server/db/database-schema.ts similarity index 97% rename from packages/bsky/src/db/database-schema.ts rename to packages/bsky/src/data-plane/server/db/database-schema.ts index df28c8b91d8..e02e07f7ad0 100644 --- a/packages/bsky/src/db/database-schema.ts +++ b/packages/bsky/src/data-plane/server/db/database-schema.ts @@ -24,6 +24,7 @@ import * as actorSync from './tables/actor-sync' import * as record from './tables/record' import * as notification from './tables/notification' import * as notificationPushToken from './tables/notification-push-token' +import * as didCache from './tables/did-cache' import * as moderation from './tables/moderation' import * as label from './tables/label' import * as algo from './tables/algo' @@ -58,6 +59,7 @@ export type DatabaseSchemaType = duplicateRecord.PartialDB & record.PartialDB & notification.PartialDB & notificationPushToken.PartialDB & + didCache.PartialDB & moderation.PartialDB & label.PartialDB & algo.PartialDB & diff --git a/packages/bsky/src/db/primary.ts b/packages/bsky/src/data-plane/server/db/db.ts similarity index 55% rename from packages/bsky/src/db/primary.ts rename to packages/bsky/src/data-plane/server/db/db.ts index e6e69872fd5..6411938d69d 100644 --- a/packages/bsky/src/db/primary.ts +++ b/packages/bsky/src/data-plane/server/db/db.ts @@ -1,35 +1,77 @@ +import assert from 'assert' import EventEmitter from 'events' import { - Migrator, + Kysely, KyselyPlugin, + Migrator, PluginTransformQueryArgs, PluginTransformResultArgs, - RootOperationNode, + PostgresDialect, QueryResult, + RootOperationNode, UnknownRow, - sql, } from 'kysely' -import { Pool as PgPool } from 'pg' import TypedEmitter from 'typed-emitter' -import { wait } from '@atproto/common' -import DatabaseSchema from './database-schema' +import { Pool as PgPool, types as pgTypes } from 'pg' import * as migrations from './migrations' -import { CtxMigrationProvider } from './migrations/provider' -import { dbLogger as log } from '../logger' +import DatabaseSchema, { DatabaseSchemaType } from './database-schema' import { PgOptions } from './types' -import { Database } from './db' +import { dbLogger } from '../../../logger' +import { CtxMigrationProvider } from './migrations/provider' -export class PrimaryDatabase extends Database { +export class Database { + pool: PgPool + db: DatabaseSchema migrator: Migrator txEvt = new EventEmitter() as TxnEmitter destroyed = false - isPrimary = true constructor( public opts: PgOptions, - instances?: { db: DatabaseSchema; pool: PgPool }, + instances?: { db: DatabaseSchema; pool: PgPool; migrator: Migrator }, ) { - super(opts, instances) + // if instances are provided, use those + if (instances) { + this.db = instances.db + this.pool = instances.pool + this.migrator = instances.migrator + return + } + + // else create a pool & connect + const { schema, url } = opts + const pool = + opts.pool ?? + new PgPool({ + connectionString: url, + max: opts.poolSize, + maxUses: opts.poolMaxUses, + idleTimeoutMillis: opts.poolIdleTimeoutMs, + }) + + // Select count(*) and other pg bigints as js integer + pgTypes.setTypeParser(pgTypes.builtins.INT8, (n) => parseInt(n, 10)) + + // Setup schema usage, primarily for test parallelism (each test suite runs in its own pg schema) + if (schema && !/^[a-z_]+$/i.test(schema)) { + throw new Error(`Postgres schema must only contain [A-Za-z_]: ${schema}`) + } + + pool.on('error', onPoolError) + pool.on('connect', (client) => { + client.on('error', onClientError) + // Used for trigram indexes, e.g. on actor search + client.query('SET pg_trgm.word_similarity_threshold TO .4;') + if (schema) { + // Shared objects such as extensions will go in the public schema + client.query(`SET search_path TO "${schema}",public;`) + } + }) + + this.pool = pool + this.db = new Kysely({ + dialect: new PostgresDialect({ pool }), + }) this.migrator = new Migrator({ db: this.db, migrationTableSchema: opts.schema, @@ -37,23 +79,20 @@ export class PrimaryDatabase extends Database { }) } - static is(db: Database): db is PrimaryDatabase { - return db.isPrimary - } - - asPrimary(): PrimaryDatabase { - return this + get schema(): string | undefined { + return this.opts.schema } - async transaction(fn: (db: PrimaryDatabase) => Promise): Promise { + async transaction(fn: (db: Database) => Promise): Promise { const leakyTxPlugin = new LeakyTxPlugin() const { dbTxn, txRes } = await this.db .withPlugin(leakyTxPlugin) .transaction() .execute(async (txn) => { - const dbTxn = new PrimaryDatabase(this.opts, { + const dbTxn = new Database(this.opts, { db: txn, pool: this.pool, + migrator: this.migrator, }) const txRes = await fn(dbTxn) .catch(async (err) => { @@ -69,17 +108,23 @@ export class PrimaryDatabase extends Database { return txRes } + get isTransaction() { + return this.db.isTransaction + } + + assertTransaction() { + assert(this.isTransaction, 'Transaction required') + } + + assertNotTransaction() { + assert(!this.isTransaction, 'Cannot be in a transaction') + } + onCommit(fn: () => void) { this.assertTransaction() this.txEvt.once('commit', fn) } - async close(): Promise { - if (this.destroyed) return - await this.db.destroy() - this.destroyed = true - } - async migrateToOrThrow(migration: string) { if (this.schema) { await this.db.schema.createSchema(this.schema).ifNotExists().execute() @@ -108,48 +153,17 @@ export class PrimaryDatabase extends Database { return results } - async maintainMaterializedViews(opts: { - views: string[] - intervalSec: number - signal: AbortSignal - }) { - const { views, intervalSec, signal } = opts - while (!signal.aborted) { - // super basic synchronization by agreeing when the intervals land relative to unix timestamp - const now = Date.now() - const intervalMs = 1000 * intervalSec - const nextIteration = Math.ceil(now / intervalMs) - const nextInMs = nextIteration * intervalMs - now - await wait(nextInMs) - if (signal.aborted) break - await Promise.all( - views.map(async (view) => { - try { - await this.refreshMaterializedView(view) - log.info( - { view, time: new Date().toISOString() }, - 'materialized view refreshed', - ) - } catch (err) { - log.error( - { view, err, time: new Date().toISOString() }, - 'materialized view refresh failed', - ) - } - }), - ) - } - } - - async refreshMaterializedView(view: string) { - const { ref } = this.db.dynamic - await sql`refresh materialized view concurrently ${ref(view)}`.execute( - this.db, - ) + async close(): Promise { + if (this.destroyed) return + await this.db.destroy() + this.destroyed = true } } -export default PrimaryDatabase +export default Database + +const onPoolError = (err: Error) => dbLogger.error({ err }, 'db pool error') +const onClientError = (err: Error) => dbLogger.error({ err }, 'db client error') // utils // ------- diff --git a/packages/bsky/src/data-plane/server/db/index.ts b/packages/bsky/src/data-plane/server/db/index.ts new file mode 100644 index 00000000000..1beb455f5e3 --- /dev/null +++ b/packages/bsky/src/data-plane/server/db/index.ts @@ -0,0 +1 @@ +export * from './db' diff --git a/packages/bsky/src/db/migrations/20230309T045948368Z-init.ts b/packages/bsky/src/data-plane/server/db/migrations/20230309T045948368Z-init.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230309T045948368Z-init.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230309T045948368Z-init.ts diff --git a/packages/bsky/src/db/migrations/20230408T152211201Z-notification-init.ts b/packages/bsky/src/data-plane/server/db/migrations/20230408T152211201Z-notification-init.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230408T152211201Z-notification-init.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230408T152211201Z-notification-init.ts diff --git a/packages/bsky/src/db/migrations/20230417T210628672Z-moderation-init.ts b/packages/bsky/src/data-plane/server/db/migrations/20230417T210628672Z-moderation-init.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230417T210628672Z-moderation-init.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230417T210628672Z-moderation-init.ts diff --git a/packages/bsky/src/db/migrations/20230420T211446071Z-did-cache.ts b/packages/bsky/src/data-plane/server/db/migrations/20230420T211446071Z-did-cache.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230420T211446071Z-did-cache.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230420T211446071Z-did-cache.ts diff --git a/packages/bsky/src/db/migrations/20230427T194702079Z-notif-record-index.ts b/packages/bsky/src/data-plane/server/db/migrations/20230427T194702079Z-notif-record-index.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230427T194702079Z-notif-record-index.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230427T194702079Z-notif-record-index.ts diff --git a/packages/bsky/src/db/migrations/20230605T144730094Z-post-profile-aggs.ts b/packages/bsky/src/data-plane/server/db/migrations/20230605T144730094Z-post-profile-aggs.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230605T144730094Z-post-profile-aggs.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230605T144730094Z-post-profile-aggs.ts diff --git a/packages/bsky/src/db/migrations/20230607T211442112Z-feed-generator-init.ts b/packages/bsky/src/data-plane/server/db/migrations/20230607T211442112Z-feed-generator-init.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230607T211442112Z-feed-generator-init.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230607T211442112Z-feed-generator-init.ts diff --git a/packages/bsky/src/db/migrations/20230608T155101190Z-algo-whats-hot-view.ts b/packages/bsky/src/data-plane/server/db/migrations/20230608T155101190Z-algo-whats-hot-view.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230608T155101190Z-algo-whats-hot-view.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230608T155101190Z-algo-whats-hot-view.ts diff --git a/packages/bsky/src/db/migrations/20230608T201813132Z-mute-lists.ts b/packages/bsky/src/data-plane/server/db/migrations/20230608T201813132Z-mute-lists.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230608T201813132Z-mute-lists.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230608T201813132Z-mute-lists.ts diff --git a/packages/bsky/src/db/migrations/20230608T205147239Z-mutes.ts b/packages/bsky/src/data-plane/server/db/migrations/20230608T205147239Z-mutes.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230608T205147239Z-mutes.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230608T205147239Z-mutes.ts diff --git a/packages/bsky/src/db/migrations/20230609T153623961Z-blocks.ts b/packages/bsky/src/data-plane/server/db/migrations/20230609T153623961Z-blocks.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230609T153623961Z-blocks.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230609T153623961Z-blocks.ts diff --git a/packages/bsky/src/db/migrations/20230609T232122649Z-actor-deletion-indexes.ts b/packages/bsky/src/data-plane/server/db/migrations/20230609T232122649Z-actor-deletion-indexes.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230609T232122649Z-actor-deletion-indexes.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230609T232122649Z-actor-deletion-indexes.ts diff --git a/packages/bsky/src/db/migrations/20230610T203555962Z-suggested-follows.ts b/packages/bsky/src/data-plane/server/db/migrations/20230610T203555962Z-suggested-follows.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230610T203555962Z-suggested-follows.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230610T203555962Z-suggested-follows.ts diff --git a/packages/bsky/src/db/migrations/20230611T215300060Z-actor-state.ts b/packages/bsky/src/data-plane/server/db/migrations/20230611T215300060Z-actor-state.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230611T215300060Z-actor-state.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230611T215300060Z-actor-state.ts diff --git a/packages/bsky/src/db/migrations/20230620T161134972Z-post-langs.ts b/packages/bsky/src/data-plane/server/db/migrations/20230620T161134972Z-post-langs.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230620T161134972Z-post-langs.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230620T161134972Z-post-langs.ts diff --git a/packages/bsky/src/db/migrations/20230627T212437895Z-optional-handle.ts b/packages/bsky/src/data-plane/server/db/migrations/20230627T212437895Z-optional-handle.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230627T212437895Z-optional-handle.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230627T212437895Z-optional-handle.ts diff --git a/packages/bsky/src/db/migrations/20230629T220835893Z-remove-post-hierarchy.ts b/packages/bsky/src/data-plane/server/db/migrations/20230629T220835893Z-remove-post-hierarchy.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230629T220835893Z-remove-post-hierarchy.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230629T220835893Z-remove-post-hierarchy.ts diff --git a/packages/bsky/src/db/migrations/20230703T045536691Z-feed-and-label-indices.ts b/packages/bsky/src/data-plane/server/db/migrations/20230703T045536691Z-feed-and-label-indices.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230703T045536691Z-feed-and-label-indices.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230703T045536691Z-feed-and-label-indices.ts diff --git a/packages/bsky/src/db/migrations/20230720T164800037Z-posts-cursor-idx.ts b/packages/bsky/src/data-plane/server/db/migrations/20230720T164800037Z-posts-cursor-idx.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230720T164800037Z-posts-cursor-idx.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230720T164800037Z-posts-cursor-idx.ts diff --git a/packages/bsky/src/db/migrations/20230807T035309811Z-feed-item-delete-invite-for-user-idx.ts b/packages/bsky/src/data-plane/server/db/migrations/20230807T035309811Z-feed-item-delete-invite-for-user-idx.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230807T035309811Z-feed-item-delete-invite-for-user-idx.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230807T035309811Z-feed-item-delete-invite-for-user-idx.ts diff --git a/packages/bsky/src/db/migrations/20230808T172902639Z-repo-rev.ts b/packages/bsky/src/data-plane/server/db/migrations/20230808T172902639Z-repo-rev.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230808T172902639Z-repo-rev.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230808T172902639Z-repo-rev.ts diff --git a/packages/bsky/src/db/migrations/20230810T203349843Z-action-duration.ts b/packages/bsky/src/data-plane/server/db/migrations/20230810T203349843Z-action-duration.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230810T203349843Z-action-duration.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230810T203349843Z-action-duration.ts diff --git a/packages/bsky/src/db/migrations/20230817T195936007Z-native-notifications.ts b/packages/bsky/src/data-plane/server/db/migrations/20230817T195936007Z-native-notifications.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230817T195936007Z-native-notifications.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230817T195936007Z-native-notifications.ts diff --git a/packages/bsky/src/db/migrations/20230830T205507322Z-suggested-feeds.ts b/packages/bsky/src/data-plane/server/db/migrations/20230830T205507322Z-suggested-feeds.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230830T205507322Z-suggested-feeds.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230830T205507322Z-suggested-feeds.ts diff --git a/packages/bsky/src/db/migrations/20230904T211011773Z-block-lists.ts b/packages/bsky/src/data-plane/server/db/migrations/20230904T211011773Z-block-lists.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230904T211011773Z-block-lists.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230904T211011773Z-block-lists.ts diff --git a/packages/bsky/src/db/migrations/20230906T222220386Z-thread-gating.ts b/packages/bsky/src/data-plane/server/db/migrations/20230906T222220386Z-thread-gating.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230906T222220386Z-thread-gating.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230906T222220386Z-thread-gating.ts diff --git a/packages/bsky/src/db/migrations/20230920T213858047Z-add-tags-to-post.ts b/packages/bsky/src/data-plane/server/db/migrations/20230920T213858047Z-add-tags-to-post.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230920T213858047Z-add-tags-to-post.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230920T213858047Z-add-tags-to-post.ts diff --git a/packages/bsky/src/db/migrations/20230929T192920807Z-record-cursor-indexes.ts b/packages/bsky/src/data-plane/server/db/migrations/20230929T192920807Z-record-cursor-indexes.ts similarity index 100% rename from packages/bsky/src/db/migrations/20230929T192920807Z-record-cursor-indexes.ts rename to packages/bsky/src/data-plane/server/db/migrations/20230929T192920807Z-record-cursor-indexes.ts diff --git a/packages/bsky/src/db/migrations/20231003T202833377Z-create-moderation-subject-status.ts b/packages/bsky/src/data-plane/server/db/migrations/20231003T202833377Z-create-moderation-subject-status.ts similarity index 100% rename from packages/bsky/src/db/migrations/20231003T202833377Z-create-moderation-subject-status.ts rename to packages/bsky/src/data-plane/server/db/migrations/20231003T202833377Z-create-moderation-subject-status.ts diff --git a/packages/bsky/src/db/migrations/20231220T225126090Z-blob-takedowns.ts b/packages/bsky/src/data-plane/server/db/migrations/20231220T225126090Z-blob-takedowns.ts similarity index 100% rename from packages/bsky/src/db/migrations/20231220T225126090Z-blob-takedowns.ts rename to packages/bsky/src/data-plane/server/db/migrations/20231220T225126090Z-blob-takedowns.ts diff --git a/packages/bsky/src/db/migrations/20240124T023719200Z-tagged-suggestions.ts b/packages/bsky/src/data-plane/server/db/migrations/20240124T023719200Z-tagged-suggestions.ts similarity index 100% rename from packages/bsky/src/db/migrations/20240124T023719200Z-tagged-suggestions.ts rename to packages/bsky/src/data-plane/server/db/migrations/20240124T023719200Z-tagged-suggestions.ts diff --git a/packages/bsky/src/db/migrations/index.ts b/packages/bsky/src/data-plane/server/db/migrations/index.ts similarity index 97% rename from packages/bsky/src/db/migrations/index.ts rename to packages/bsky/src/data-plane/server/db/migrations/index.ts index e5a5b155e71..03f8795b3f7 100644 --- a/packages/bsky/src/db/migrations/index.ts +++ b/packages/bsky/src/data-plane/server/db/migrations/index.ts @@ -31,6 +31,5 @@ export * as _20230906T222220386Z from './20230906T222220386Z-thread-gating' export * as _20230920T213858047Z from './20230920T213858047Z-add-tags-to-post' export * as _20230929T192920807Z from './20230929T192920807Z-record-cursor-indexes' export * as _20231003T202833377Z from './20231003T202833377Z-create-moderation-subject-status' -export * as _20231205T000257238Z from './20231205T000257238Z-remove-did-cache' export * as _20231220T225126090Z from './20231220T225126090Z-blob-takedowns' export * as _20240124T023719200Z from './20240124T023719200Z-tagged-suggestions' diff --git a/packages/bsky/src/db/migrations/provider.ts b/packages/bsky/src/data-plane/server/db/migrations/provider.ts similarity index 100% rename from packages/bsky/src/db/migrations/provider.ts rename to packages/bsky/src/data-plane/server/db/migrations/provider.ts diff --git a/packages/bsky/src/db/pagination.ts b/packages/bsky/src/data-plane/server/db/pagination.ts similarity index 88% rename from packages/bsky/src/db/pagination.ts rename to packages/bsky/src/data-plane/server/db/pagination.ts index f08702cb003..d4c5747e2b4 100644 --- a/packages/bsky/src/db/pagination.ts +++ b/packages/bsky/src/data-plane/server/db/pagination.ts @@ -16,7 +16,7 @@ export type LabeledResult = { * - LabeledResult: a Result processed such that the "primary" and "secondary" parts of the cursor are labeled. * - E.g. { primary: '2022-01-01T12:00:00Z', secondary: 'bafyx' } * - Cursor: the two string parts that make-up the packed/string cursor. - * - E.g. packed cursor '1641038400000::bafyx' in parts { primary: '1641038400000', secondary: 'bafyx' } + * - E.g. packed cursor '1641038400000__bafyx' in parts { primary: '1641038400000', secondary: 'bafyx' } * * These types relate as such. Implementers define the relations marked with a *: * Result -*-> LabeledResult <-*-> Cursor <--> packed/string cursor @@ -27,9 +27,6 @@ export abstract class GenericKeyset { abstract labelResult(result: R): LR abstract labeledResultToCursor(labeled: LR): Cursor abstract cursorToLabeledResult(cursor: Cursor): LR - static clearlyBad(cursor?: string) { - return cursor !== undefined && !cursor.includes('::') - } packFromResult(results: R | R[]): string | undefined { const result = Array.isArray(results) ? results.at(-1) : results if (!result) return @@ -47,11 +44,11 @@ export abstract class GenericKeyset { } packCursor(cursor?: Cursor): string | undefined { if (!cursor) return - return `${cursor.primary}::${cursor.secondary}` + return `${cursor.primary}__${cursor.secondary}` } unpackCursor(cursorStr?: string): Cursor | undefined { if (!cursorStr) return - const result = cursorStr.split('::') + const result = cursorStr.split('__') const [primary, secondary, ...others] = result if (!primary || !secondary || others.length > 0) { throw new InvalidRequestError('Malformed cursor') @@ -109,6 +106,24 @@ export class TimeCidKeyset< } } +export class CreatedAtDidKeyset extends TimeCidKeyset<{ + createdAt: string + did: string // dids are treated identically to cids in TimeCidKeyset +}> { + labelResult(result: { createdAt: string; did: string }) { + return { primary: result.createdAt, secondary: result.did } + } +} + +export class IndexedAtDidKeyset extends TimeCidKeyset<{ + indexedAt: string + did: string // dids are treated identically to cids in TimeCidKeyset +}> { + labelResult(result: { indexedAt: string; did: string }) { + return { primary: result.indexedAt, secondary: result.did } + } +} + export const paginate = < QB extends AnyQb, K extends GenericKeyset, diff --git a/packages/bsky/src/db/tables/actor-block.ts b/packages/bsky/src/data-plane/server/db/tables/actor-block.ts similarity index 100% rename from packages/bsky/src/db/tables/actor-block.ts rename to packages/bsky/src/data-plane/server/db/tables/actor-block.ts diff --git a/packages/bsky/src/db/tables/actor-state.ts b/packages/bsky/src/data-plane/server/db/tables/actor-state.ts similarity index 100% rename from packages/bsky/src/db/tables/actor-state.ts rename to packages/bsky/src/data-plane/server/db/tables/actor-state.ts diff --git a/packages/bsky/src/db/tables/actor-sync.ts b/packages/bsky/src/data-plane/server/db/tables/actor-sync.ts similarity index 100% rename from packages/bsky/src/db/tables/actor-sync.ts rename to packages/bsky/src/data-plane/server/db/tables/actor-sync.ts diff --git a/packages/bsky/src/db/tables/actor.ts b/packages/bsky/src/data-plane/server/db/tables/actor.ts similarity index 100% rename from packages/bsky/src/db/tables/actor.ts rename to packages/bsky/src/data-plane/server/db/tables/actor.ts diff --git a/packages/bsky/src/db/tables/algo.ts b/packages/bsky/src/data-plane/server/db/tables/algo.ts similarity index 100% rename from packages/bsky/src/db/tables/algo.ts rename to packages/bsky/src/data-plane/server/db/tables/algo.ts diff --git a/packages/bsky/src/db/tables/blob-takedown.ts b/packages/bsky/src/data-plane/server/db/tables/blob-takedown.ts similarity index 100% rename from packages/bsky/src/db/tables/blob-takedown.ts rename to packages/bsky/src/data-plane/server/db/tables/blob-takedown.ts diff --git a/packages/bsky/src/data-plane/server/db/tables/did-cache.ts b/packages/bsky/src/data-plane/server/db/tables/did-cache.ts new file mode 100644 index 00000000000..b3865548725 --- /dev/null +++ b/packages/bsky/src/data-plane/server/db/tables/did-cache.ts @@ -0,0 +1,13 @@ +import { DidDocument } from '@atproto/identity' + +export interface DidCache { + did: string + doc: DidDocument + updatedAt: number +} + +export const tableName = 'did_cache' + +export type PartialDB = { + [tableName]: DidCache +} diff --git a/packages/bsky/src/db/tables/duplicate-record.ts b/packages/bsky/src/data-plane/server/db/tables/duplicate-record.ts similarity index 100% rename from packages/bsky/src/db/tables/duplicate-record.ts rename to packages/bsky/src/data-plane/server/db/tables/duplicate-record.ts diff --git a/packages/bsky/src/db/tables/feed-generator.ts b/packages/bsky/src/data-plane/server/db/tables/feed-generator.ts similarity index 100% rename from packages/bsky/src/db/tables/feed-generator.ts rename to packages/bsky/src/data-plane/server/db/tables/feed-generator.ts diff --git a/packages/bsky/src/db/tables/feed-item.ts b/packages/bsky/src/data-plane/server/db/tables/feed-item.ts similarity index 100% rename from packages/bsky/src/db/tables/feed-item.ts rename to packages/bsky/src/data-plane/server/db/tables/feed-item.ts diff --git a/packages/bsky/src/db/tables/follow.ts b/packages/bsky/src/data-plane/server/db/tables/follow.ts similarity index 100% rename from packages/bsky/src/db/tables/follow.ts rename to packages/bsky/src/data-plane/server/db/tables/follow.ts diff --git a/packages/bsky/src/db/tables/label.ts b/packages/bsky/src/data-plane/server/db/tables/label.ts similarity index 100% rename from packages/bsky/src/db/tables/label.ts rename to packages/bsky/src/data-plane/server/db/tables/label.ts diff --git a/packages/bsky/src/db/tables/like.ts b/packages/bsky/src/data-plane/server/db/tables/like.ts similarity index 100% rename from packages/bsky/src/db/tables/like.ts rename to packages/bsky/src/data-plane/server/db/tables/like.ts diff --git a/packages/bsky/src/db/tables/list-block.ts b/packages/bsky/src/data-plane/server/db/tables/list-block.ts similarity index 100% rename from packages/bsky/src/db/tables/list-block.ts rename to packages/bsky/src/data-plane/server/db/tables/list-block.ts diff --git a/packages/bsky/src/db/tables/list-item.ts b/packages/bsky/src/data-plane/server/db/tables/list-item.ts similarity index 100% rename from packages/bsky/src/db/tables/list-item.ts rename to packages/bsky/src/data-plane/server/db/tables/list-item.ts diff --git a/packages/bsky/src/db/tables/list-mute.ts b/packages/bsky/src/data-plane/server/db/tables/list-mute.ts similarity index 100% rename from packages/bsky/src/db/tables/list-mute.ts rename to packages/bsky/src/data-plane/server/db/tables/list-mute.ts diff --git a/packages/bsky/src/db/tables/list.ts b/packages/bsky/src/data-plane/server/db/tables/list.ts similarity index 100% rename from packages/bsky/src/db/tables/list.ts rename to packages/bsky/src/data-plane/server/db/tables/list.ts diff --git a/packages/bsky/src/db/tables/moderation.ts b/packages/bsky/src/data-plane/server/db/tables/moderation.ts similarity index 96% rename from packages/bsky/src/db/tables/moderation.ts rename to packages/bsky/src/data-plane/server/db/tables/moderation.ts index f1ac3572785..c483ae20a4c 100644 --- a/packages/bsky/src/db/tables/moderation.ts +++ b/packages/bsky/src/data-plane/server/db/tables/moderation.ts @@ -3,7 +3,7 @@ import { REVIEWCLOSED, REVIEWOPEN, REVIEWESCALATED, -} from '../../lexicon/types/com/atproto/admin/defs' +} from '../../../../lexicon/types/com/atproto/admin/defs' export const eventTableName = 'moderation_event' export const subjectStatusTableName = 'moderation_subject_status' diff --git a/packages/bsky/src/db/tables/mute.ts b/packages/bsky/src/data-plane/server/db/tables/mute.ts similarity index 100% rename from packages/bsky/src/db/tables/mute.ts rename to packages/bsky/src/data-plane/server/db/tables/mute.ts diff --git a/packages/bsky/src/db/tables/notification-push-token.ts b/packages/bsky/src/data-plane/server/db/tables/notification-push-token.ts similarity index 100% rename from packages/bsky/src/db/tables/notification-push-token.ts rename to packages/bsky/src/data-plane/server/db/tables/notification-push-token.ts diff --git a/packages/bsky/src/db/tables/notification.ts b/packages/bsky/src/data-plane/server/db/tables/notification.ts similarity index 100% rename from packages/bsky/src/db/tables/notification.ts rename to packages/bsky/src/data-plane/server/db/tables/notification.ts diff --git a/packages/bsky/src/db/tables/post-agg.ts b/packages/bsky/src/data-plane/server/db/tables/post-agg.ts similarity index 100% rename from packages/bsky/src/db/tables/post-agg.ts rename to packages/bsky/src/data-plane/server/db/tables/post-agg.ts diff --git a/packages/bsky/src/db/tables/post-embed.ts b/packages/bsky/src/data-plane/server/db/tables/post-embed.ts similarity index 100% rename from packages/bsky/src/db/tables/post-embed.ts rename to packages/bsky/src/data-plane/server/db/tables/post-embed.ts diff --git a/packages/bsky/src/db/tables/post.ts b/packages/bsky/src/data-plane/server/db/tables/post.ts similarity index 100% rename from packages/bsky/src/db/tables/post.ts rename to packages/bsky/src/data-plane/server/db/tables/post.ts diff --git a/packages/bsky/src/db/tables/profile-agg.ts b/packages/bsky/src/data-plane/server/db/tables/profile-agg.ts similarity index 100% rename from packages/bsky/src/db/tables/profile-agg.ts rename to packages/bsky/src/data-plane/server/db/tables/profile-agg.ts diff --git a/packages/bsky/src/db/tables/profile.ts b/packages/bsky/src/data-plane/server/db/tables/profile.ts similarity index 100% rename from packages/bsky/src/db/tables/profile.ts rename to packages/bsky/src/data-plane/server/db/tables/profile.ts diff --git a/packages/bsky/src/db/tables/record.ts b/packages/bsky/src/data-plane/server/db/tables/record.ts similarity index 100% rename from packages/bsky/src/db/tables/record.ts rename to packages/bsky/src/data-plane/server/db/tables/record.ts diff --git a/packages/bsky/src/db/tables/repost.ts b/packages/bsky/src/data-plane/server/db/tables/repost.ts similarity index 100% rename from packages/bsky/src/db/tables/repost.ts rename to packages/bsky/src/data-plane/server/db/tables/repost.ts diff --git a/packages/bsky/src/db/tables/subscription.ts b/packages/bsky/src/data-plane/server/db/tables/subscription.ts similarity index 100% rename from packages/bsky/src/db/tables/subscription.ts rename to packages/bsky/src/data-plane/server/db/tables/subscription.ts diff --git a/packages/bsky/src/db/tables/suggested-feed.ts b/packages/bsky/src/data-plane/server/db/tables/suggested-feed.ts similarity index 100% rename from packages/bsky/src/db/tables/suggested-feed.ts rename to packages/bsky/src/data-plane/server/db/tables/suggested-feed.ts diff --git a/packages/bsky/src/db/tables/suggested-follow.ts b/packages/bsky/src/data-plane/server/db/tables/suggested-follow.ts similarity index 100% rename from packages/bsky/src/db/tables/suggested-follow.ts rename to packages/bsky/src/data-plane/server/db/tables/suggested-follow.ts diff --git a/packages/bsky/src/db/tables/tagged-suggestion.ts b/packages/bsky/src/data-plane/server/db/tables/tagged-suggestion.ts similarity index 100% rename from packages/bsky/src/db/tables/tagged-suggestion.ts rename to packages/bsky/src/data-plane/server/db/tables/tagged-suggestion.ts diff --git a/packages/bsky/src/db/tables/thread-gate.ts b/packages/bsky/src/data-plane/server/db/tables/thread-gate.ts similarity index 100% rename from packages/bsky/src/db/tables/thread-gate.ts rename to packages/bsky/src/data-plane/server/db/tables/thread-gate.ts diff --git a/packages/bsky/src/db/tables/view-param.ts b/packages/bsky/src/data-plane/server/db/tables/view-param.ts similarity index 100% rename from packages/bsky/src/db/tables/view-param.ts rename to packages/bsky/src/data-plane/server/db/tables/view-param.ts diff --git a/packages/bsky/src/db/types.ts b/packages/bsky/src/data-plane/server/db/types.ts similarity index 100% rename from packages/bsky/src/db/types.ts rename to packages/bsky/src/data-plane/server/db/types.ts diff --git a/packages/bsky/src/db/util.ts b/packages/bsky/src/data-plane/server/db/util.ts similarity index 100% rename from packages/bsky/src/db/util.ts rename to packages/bsky/src/data-plane/server/db/util.ts diff --git a/packages/bsky/src/data-plane/server/index.ts b/packages/bsky/src/data-plane/server/index.ts new file mode 100644 index 00000000000..f925de83c48 --- /dev/null +++ b/packages/bsky/src/data-plane/server/index.ts @@ -0,0 +1,36 @@ +import http from 'http' +import events from 'events' +import express from 'express' +import { expressConnectMiddleware } from '@connectrpc/connect-express' +import createRoutes from './routes' +import { Database } from './db' +import { IdResolver, MemoryCache } from '@atproto/identity' + +export { RepoSubscription } from './subscription' + +export class DataPlaneServer { + constructor(public server: http.Server, public idResolver: IdResolver) {} + + static async create(db: Database, port: number, plcUrl?: string) { + const app = express() + const didCache = new MemoryCache() + const idResolver = new IdResolver({ plcUrl, didCache }) + const routes = createRoutes(db, idResolver) + app.use(expressConnectMiddleware({ routes })) + const server = app.listen(port) + await events.once(server, 'listening') + return new DataPlaneServer(server, idResolver) + } + + async destroy() { + return new Promise((resolve, reject) => { + this.server.close((err) => { + if (err) { + reject(err) + } else { + resolve() + } + }) + }) + } +} diff --git a/packages/bsky/src/services/indexing/index.ts b/packages/bsky/src/data-plane/server/indexing/index.ts similarity index 86% rename from packages/bsky/src/services/indexing/index.ts rename to packages/bsky/src/data-plane/server/indexing/index.ts index 44dd9c3c986..68f5ff8b721 100644 --- a/packages/bsky/src/services/indexing/index.ts +++ b/packages/bsky/src/data-plane/server/indexing/index.ts @@ -13,7 +13,8 @@ import { AtUri } from '@atproto/syntax' import { IdResolver, getPds } from '@atproto/identity' import { DAY, HOUR } from '@atproto/common' import { ValidationError } from '@atproto/lexicon' -import { PrimaryDatabase } from '../../db' +import { Database } from '../db' +import { Actor } from '../db/tables/actor' import * as Post from './plugins/post' import * as Threadgate from './plugins/thread-gate' import * as Like from './plugins/like' @@ -26,12 +27,9 @@ import * as ListBlock from './plugins/list-block' import * as Block from './plugins/block' import * as FeedGenerator from './plugins/feed-generator' import RecordProcessor from './processor' -import { subLogger } from '../../logger' -import { retryHttp } from '../../util/retry' -import { BackgroundQueue } from '../../background' -import { NotificationServer } from '../../notifications' -import { AutoModerator } from '../../auto-moderator' -import { Actor } from '../../db/tables/actor' +import { subLogger } from '../../../logger' +import { retryHttp } from '../../../util/retry' +import { BackgroundQueue } from '../background' export class IndexingService { records: { @@ -49,50 +47,28 @@ export class IndexingService { } constructor( - public db: PrimaryDatabase, + public db: Database, public idResolver: IdResolver, - public autoMod: AutoModerator, - public backgroundQueue: BackgroundQueue, - public notifServer?: NotificationServer, + public background: BackgroundQueue, ) { this.records = { - post: Post.makePlugin(this.db, backgroundQueue, notifServer), - threadGate: Threadgate.makePlugin(this.db, backgroundQueue, notifServer), - like: Like.makePlugin(this.db, backgroundQueue, notifServer), - repost: Repost.makePlugin(this.db, backgroundQueue, notifServer), - follow: Follow.makePlugin(this.db, backgroundQueue, notifServer), - profile: Profile.makePlugin(this.db, backgroundQueue, notifServer), - list: List.makePlugin(this.db, backgroundQueue, notifServer), - listItem: ListItem.makePlugin(this.db, backgroundQueue, notifServer), - listBlock: ListBlock.makePlugin(this.db, backgroundQueue, notifServer), - block: Block.makePlugin(this.db, backgroundQueue, notifServer), - feedGenerator: FeedGenerator.makePlugin( - this.db, - backgroundQueue, - notifServer, - ), + post: Post.makePlugin(this.db, this.background), + threadGate: Threadgate.makePlugin(this.db, this.background), + like: Like.makePlugin(this.db, this.background), + repost: Repost.makePlugin(this.db, this.background), + follow: Follow.makePlugin(this.db, this.background), + profile: Profile.makePlugin(this.db, this.background), + list: List.makePlugin(this.db, this.background), + listItem: ListItem.makePlugin(this.db, this.background), + listBlock: ListBlock.makePlugin(this.db, this.background), + block: Block.makePlugin(this.db, this.background), + feedGenerator: FeedGenerator.makePlugin(this.db, this.background), } } - transact(txn: PrimaryDatabase) { + transact(txn: Database) { txn.assertTransaction() - return new IndexingService( - txn, - this.idResolver, - this.autoMod, - this.backgroundQueue, - this.notifServer, - ) - } - - static creator( - idResolver: IdResolver, - autoMod: AutoModerator, - backgroundQueue: BackgroundQueue, - notifServer?: NotificationServer, - ) { - return (db: PrimaryDatabase) => - new IndexingService(db, idResolver, autoMod, backgroundQueue, notifServer) + return new IndexingService(txn, this.idResolver, this.background) } async indexRecord( @@ -114,9 +90,6 @@ export class IndexingService { await indexer.updateRecord(uri, cid, obj, timestamp) } }) - if (!opts?.disableLabels) { - this.autoMod.processRecord(uri, cid, obj) - } } async deleteRecord(uri: AtUri, cascading = false) { @@ -170,10 +143,6 @@ export class IndexingService { .onConflict((oc) => oc.column('did').doUpdateSet(actorInfo)) .returning('did') .executeTakeFirst() - - if (handle) { - this.autoMod.processHandle(handle, did) - } } async indexRepo(did: string, commit?: string) { diff --git a/packages/bsky/src/services/indexing/plugins/block.ts b/packages/bsky/src/data-plane/server/indexing/plugins/block.ts similarity index 77% rename from packages/bsky/src/services/indexing/plugins/block.ts rename to packages/bsky/src/data-plane/server/indexing/plugins/block.ts index 88e62b6f5ac..ec4956a04f5 100644 --- a/packages/bsky/src/services/indexing/plugins/block.ts +++ b/packages/bsky/src/data-plane/server/indexing/plugins/block.ts @@ -1,13 +1,12 @@ import { Selectable } from 'kysely' import { AtUri, normalizeDatetimeAlways } from '@atproto/syntax' import { CID } from 'multiformats/cid' -import * as Block from '../../../lexicon/types/app/bsky/graph/block' -import * as lex from '../../../lexicon/lexicons' -import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' +import * as Block from '../../../../lexicon/types/app/bsky/graph/block' +import * as lex from '../../../../lexicon/lexicons' +import { Database } from '../../db' +import { DatabaseSchema, DatabaseSchemaType } from '../../db/database-schema' import RecordProcessor from '../processor' -import { PrimaryDatabase } from '../../../db' -import { BackgroundQueue } from '../../../background' -import { NotificationServer } from '../../../notifications' +import { BackgroundQueue } from '../../background' const lexId = lex.ids.AppBskyGraphBlock type IndexedBlock = Selectable @@ -72,11 +71,10 @@ const notifsForDelete = () => { export type PluginType = RecordProcessor export const makePlugin = ( - db: PrimaryDatabase, - backgroundQueue: BackgroundQueue, - notifServer?: NotificationServer, + db: Database, + background: BackgroundQueue, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, notifServer, { + return new RecordProcessor(db, background, { lexId, insertFn, findDuplicate, diff --git a/packages/bsky/src/services/indexing/plugins/feed-generator.ts b/packages/bsky/src/data-plane/server/indexing/plugins/feed-generator.ts similarity index 77% rename from packages/bsky/src/services/indexing/plugins/feed-generator.ts rename to packages/bsky/src/data-plane/server/indexing/plugins/feed-generator.ts index be5435966f1..f3b82c75567 100644 --- a/packages/bsky/src/services/indexing/plugins/feed-generator.ts +++ b/packages/bsky/src/data-plane/server/indexing/plugins/feed-generator.ts @@ -1,13 +1,12 @@ import { Selectable } from 'kysely' import { AtUri, normalizeDatetimeAlways } from '@atproto/syntax' import { CID } from 'multiformats/cid' -import * as FeedGenerator from '../../../lexicon/types/app/bsky/feed/generator' -import * as lex from '../../../lexicon/lexicons' -import { PrimaryDatabase } from '../../../db' -import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' -import { BackgroundQueue } from '../../../background' +import * as FeedGenerator from '../../../../lexicon/types/app/bsky/feed/generator' +import * as lex from '../../../../lexicon/lexicons' +import { Database } from '../../db' +import { DatabaseSchema, DatabaseSchemaType } from '../../db/database-schema' import RecordProcessor from '../processor' -import { NotificationServer } from '../../../notifications' +import { BackgroundQueue } from '../../background' const lexId = lex.ids.AppBskyFeedGenerator type IndexedFeedGenerator = Selectable @@ -71,11 +70,10 @@ export type PluginType = RecordProcessor< > export const makePlugin = ( - db: PrimaryDatabase, - backgroundQueue: BackgroundQueue, - notifServer?: NotificationServer, + db: Database, + background: BackgroundQueue, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, notifServer, { + return new RecordProcessor(db, background, { lexId, insertFn, findDuplicate, diff --git a/packages/bsky/src/services/indexing/plugins/follow.ts b/packages/bsky/src/data-plane/server/indexing/plugins/follow.ts similarity index 84% rename from packages/bsky/src/services/indexing/plugins/follow.ts rename to packages/bsky/src/data-plane/server/indexing/plugins/follow.ts index 8655c7eba71..6f238755761 100644 --- a/packages/bsky/src/services/indexing/plugins/follow.ts +++ b/packages/bsky/src/data-plane/server/indexing/plugins/follow.ts @@ -1,14 +1,13 @@ import { Selectable } from 'kysely' import { AtUri, normalizeDatetimeAlways } from '@atproto/syntax' import { CID } from 'multiformats/cid' -import * as Follow from '../../../lexicon/types/app/bsky/graph/follow' -import * as lex from '../../../lexicon/lexicons' -import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' +import * as Follow from '../../../../lexicon/types/app/bsky/graph/follow' +import * as lex from '../../../../lexicon/lexicons' import RecordProcessor from '../processor' -import { PrimaryDatabase } from '../../../db' -import { countAll, excluded } from '../../../db/util' -import { BackgroundQueue } from '../../../background' -import { NotificationServer } from '../../../notifications' +import { Database } from '../../db' +import { countAll, excluded } from '../../db/util' +import { DatabaseSchema, DatabaseSchemaType } from '../../db/database-schema' +import { BackgroundQueue } from '../../background' const lexId = lex.ids.AppBskyGraphFollow type IndexedFollow = Selectable @@ -119,11 +118,10 @@ const updateAggregates = async (db: DatabaseSchema, follow: IndexedFollow) => { export type PluginType = RecordProcessor export const makePlugin = ( - db: PrimaryDatabase, - backgroundQueue: BackgroundQueue, - notifServer?: NotificationServer, + db: Database, + background: BackgroundQueue, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, notifServer, { + return new RecordProcessor(db, background, { lexId, insertFn, findDuplicate, diff --git a/packages/bsky/src/services/indexing/plugins/like.ts b/packages/bsky/src/data-plane/server/indexing/plugins/like.ts similarity index 83% rename from packages/bsky/src/services/indexing/plugins/like.ts rename to packages/bsky/src/data-plane/server/indexing/plugins/like.ts index 703800f67c8..98e9fc722f8 100644 --- a/packages/bsky/src/services/indexing/plugins/like.ts +++ b/packages/bsky/src/data-plane/server/indexing/plugins/like.ts @@ -1,14 +1,13 @@ import { Selectable } from 'kysely' import { AtUri, normalizeDatetimeAlways } from '@atproto/syntax' import { CID } from 'multiformats/cid' -import * as Like from '../../../lexicon/types/app/bsky/feed/like' -import * as lex from '../../../lexicon/lexicons' -import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' +import * as Like from '../../../../lexicon/types/app/bsky/feed/like' +import * as lex from '../../../../lexicon/lexicons' import RecordProcessor from '../processor' -import { countAll, excluded } from '../../../db/util' -import { PrimaryDatabase } from '../../../db' -import { BackgroundQueue } from '../../../background' -import { NotificationServer } from '../../../notifications' +import { countAll, excluded } from '../../db/util' +import { Database } from '../../db' +import { DatabaseSchema, DatabaseSchemaType } from '../../db/database-schema' +import { BackgroundQueue } from '../../background' const lexId = lex.ids.AppBskyFeedLike type IndexedLike = Selectable @@ -109,11 +108,10 @@ const updateAggregates = async (db: DatabaseSchema, like: IndexedLike) => { export type PluginType = RecordProcessor export const makePlugin = ( - db: PrimaryDatabase, - backgroundQueue: BackgroundQueue, - notifServer?: NotificationServer, + db: Database, + background: BackgroundQueue, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, notifServer, { + return new RecordProcessor(db, background, { lexId, insertFn, findDuplicate, diff --git a/packages/bsky/src/services/indexing/plugins/list-block.ts b/packages/bsky/src/data-plane/server/indexing/plugins/list-block.ts similarity index 77% rename from packages/bsky/src/services/indexing/plugins/list-block.ts rename to packages/bsky/src/data-plane/server/indexing/plugins/list-block.ts index 3040f1aa3f9..09eabcdb9f4 100644 --- a/packages/bsky/src/services/indexing/plugins/list-block.ts +++ b/packages/bsky/src/data-plane/server/indexing/plugins/list-block.ts @@ -1,13 +1,12 @@ import { Selectable } from 'kysely' import { AtUri, normalizeDatetimeAlways } from '@atproto/syntax' import { CID } from 'multiformats/cid' -import * as ListBlock from '../../../lexicon/types/app/bsky/graph/listblock' -import * as lex from '../../../lexicon/lexicons' -import { PrimaryDatabase } from '../../../db' -import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' +import * as ListBlock from '../../../../lexicon/types/app/bsky/graph/listblock' +import * as lex from '../../../../lexicon/lexicons' +import { Database } from '../../db' +import { DatabaseSchema, DatabaseSchemaType } from '../../db/database-schema' import RecordProcessor from '../processor' -import { BackgroundQueue } from '../../../background' -import { NotificationServer } from '../../../notifications' +import { BackgroundQueue } from '../../background' const lexId = lex.ids.AppBskyGraphListblock type IndexedListBlock = Selectable @@ -72,11 +71,10 @@ const notifsForDelete = () => { export type PluginType = RecordProcessor export const makePlugin = ( - db: PrimaryDatabase, - backgroundQueue: BackgroundQueue, - notifServer?: NotificationServer, + db: Database, + background: BackgroundQueue, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, notifServer, { + return new RecordProcessor(db, background, { lexId, insertFn, findDuplicate, diff --git a/packages/bsky/src/services/indexing/plugins/list-item.ts b/packages/bsky/src/data-plane/server/indexing/plugins/list-item.ts similarity index 79% rename from packages/bsky/src/services/indexing/plugins/list-item.ts rename to packages/bsky/src/data-plane/server/indexing/plugins/list-item.ts index 9e08145b23e..f2a43cff485 100644 --- a/packages/bsky/src/services/indexing/plugins/list-item.ts +++ b/packages/bsky/src/data-plane/server/indexing/plugins/list-item.ts @@ -1,14 +1,13 @@ import { Selectable } from 'kysely' +import { InvalidRequestError } from '@atproto/xrpc-server' import { AtUri, normalizeDatetimeAlways } from '@atproto/syntax' import { CID } from 'multiformats/cid' -import * as ListItem from '../../../lexicon/types/app/bsky/graph/listitem' -import * as lex from '../../../lexicon/lexicons' -import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' +import * as ListItem from '../../../../lexicon/types/app/bsky/graph/listitem' +import * as lex from '../../../../lexicon/lexicons' import RecordProcessor from '../processor' -import { InvalidRequestError } from '@atproto/xrpc-server' -import { PrimaryDatabase } from '../../../db' -import { BackgroundQueue } from '../../../background' -import { NotificationServer } from '../../../notifications' +import { Database } from '../../db' +import { DatabaseSchema, DatabaseSchemaType } from '../../db/database-schema' +import { BackgroundQueue } from '../../background' const lexId = lex.ids.AppBskyGraphListitem type IndexedListItem = Selectable @@ -80,11 +79,10 @@ const notifsForDelete = () => { export type PluginType = RecordProcessor export const makePlugin = ( - db: PrimaryDatabase, - backgroundQueue: BackgroundQueue, - notifServer?: NotificationServer, + db: Database, + background: BackgroundQueue, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, notifServer, { + return new RecordProcessor(db, background, { lexId, insertFn, findDuplicate, diff --git a/packages/bsky/src/services/indexing/plugins/list.ts b/packages/bsky/src/data-plane/server/indexing/plugins/list.ts similarity index 77% rename from packages/bsky/src/services/indexing/plugins/list.ts rename to packages/bsky/src/data-plane/server/indexing/plugins/list.ts index 0d078572501..f6deaf0a68e 100644 --- a/packages/bsky/src/services/indexing/plugins/list.ts +++ b/packages/bsky/src/data-plane/server/indexing/plugins/list.ts @@ -1,13 +1,12 @@ import { Selectable } from 'kysely' import { AtUri, normalizeDatetimeAlways } from '@atproto/syntax' import { CID } from 'multiformats/cid' -import * as List from '../../../lexicon/types/app/bsky/graph/list' -import * as lex from '../../../lexicon/lexicons' -import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' +import * as List from '../../../../lexicon/types/app/bsky/graph/list' +import * as lex from '../../../../lexicon/lexicons' +import { DatabaseSchema, DatabaseSchemaType } from '../../db/database-schema' import RecordProcessor from '../processor' -import { PrimaryDatabase } from '../../../db' -import { BackgroundQueue } from '../../../background' -import { NotificationServer } from '../../../notifications' +import { Database } from '../../db' +import { BackgroundQueue } from '../../background' const lexId = lex.ids.AppBskyGraphList type IndexedList = Selectable @@ -68,11 +67,10 @@ const notifsForDelete = () => { export type PluginType = RecordProcessor export const makePlugin = ( - db: PrimaryDatabase, - backgroundQueue: BackgroundQueue, - notifServer?: NotificationServer, + db: Database, + background: BackgroundQueue, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, notifServer, { + return new RecordProcessor(db, background, { lexId, insertFn, findDuplicate, diff --git a/packages/bsky/src/services/indexing/plugins/post.ts b/packages/bsky/src/data-plane/server/indexing/plugins/post.ts similarity index 89% rename from packages/bsky/src/services/indexing/plugins/post.ts rename to packages/bsky/src/data-plane/server/indexing/plugins/post.ts index af581b3bdff..cc4121ab667 100644 --- a/packages/bsky/src/services/indexing/plugins/post.ts +++ b/packages/bsky/src/data-plane/server/indexing/plugins/post.ts @@ -5,27 +5,30 @@ import { jsonStringToLex } from '@atproto/lexicon' import { Record as PostRecord, ReplyRef, -} from '../../../lexicon/types/app/bsky/feed/post' -import { Record as GateRecord } from '../../../lexicon/types/app/bsky/feed/threadgate' -import { isMain as isEmbedImage } from '../../../lexicon/types/app/bsky/embed/images' -import { isMain as isEmbedExternal } from '../../../lexicon/types/app/bsky/embed/external' -import { isMain as isEmbedRecord } from '../../../lexicon/types/app/bsky/embed/record' -import { isMain as isEmbedRecordWithMedia } from '../../../lexicon/types/app/bsky/embed/recordWithMedia' +} from '../../../../lexicon/types/app/bsky/feed/post' +import { Record as GateRecord } from '../../../../lexicon/types/app/bsky/feed/threadgate' +import { isMain as isEmbedImage } from '../../../../lexicon/types/app/bsky/embed/images' +import { isMain as isEmbedExternal } from '../../../../lexicon/types/app/bsky/embed/external' +import { isMain as isEmbedRecord } from '../../../../lexicon/types/app/bsky/embed/record' +import { isMain as isEmbedRecordWithMedia } from '../../../../lexicon/types/app/bsky/embed/recordWithMedia' import { isMention, isLink, -} from '../../../lexicon/types/app/bsky/richtext/facet' -import * as lex from '../../../lexicon/lexicons' -import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' +} from '../../../../lexicon/types/app/bsky/richtext/facet' +import * as lex from '../../../../lexicon/lexicons' +import { DatabaseSchema, DatabaseSchemaType } from '../../db/database-schema' import RecordProcessor from '../processor' -import { Notification } from '../../../db/tables/notification' -import { PrimaryDatabase } from '../../../db' -import { countAll, excluded } from '../../../db/util' -import { BackgroundQueue } from '../../../background' -import { getAncestorsAndSelfQb, getDescendentsQb } from '../../util/post' -import { NotificationServer } from '../../../notifications' -import * as feedutil from '../../feed/util' -import { postToThreadgateUri } from '../../feed/util' +import { Notification } from '../../db/tables/notification' +import { Database } from '../../db' +import { countAll, excluded } from '../../db/util' +import { + getAncestorsAndSelfQb, + getDescendentsQb, + invalidReplyRoot as checkInvalidReplyRoot, + violatesThreadGate as checkViolatesThreadGate, + postToThreadgateUri, +} from '../../util' +import { BackgroundQueue } from '../../background' type Notif = Insertable type Post = Selectable @@ -392,11 +395,10 @@ const updateAggregates = async (db: DatabaseSchema, postIdx: IndexedPost) => { export type PluginType = RecordProcessor export const makePlugin = ( - db: PrimaryDatabase, - backgroundQueue: BackgroundQueue, - notifServer?: NotificationServer, + db: Database, + background: BackgroundQueue, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, notifServer, { + return new RecordProcessor(db, background, { lexId, insertFn, findDuplicate, @@ -427,9 +429,9 @@ async function validateReply( const replyRefs = await getReplyRefs(db, reply) // check reply const invalidReplyRoot = - !replyRefs.parent || feedutil.invalidReplyRoot(reply, replyRefs.parent) + !replyRefs.parent || checkInvalidReplyRoot(reply, replyRefs.parent) // check interaction - const violatesThreadGate = await feedutil.violatesThreadGate( + const violatesThreadGate = await checkViolatesThreadGate( db, creator, new AtUri(reply.root.uri).hostname, diff --git a/packages/bsky/src/services/indexing/plugins/profile.ts b/packages/bsky/src/data-plane/server/indexing/plugins/profile.ts similarity index 75% rename from packages/bsky/src/services/indexing/plugins/profile.ts rename to packages/bsky/src/data-plane/server/indexing/plugins/profile.ts index ea0c8f07f98..18c9b54bbb9 100644 --- a/packages/bsky/src/services/indexing/plugins/profile.ts +++ b/packages/bsky/src/data-plane/server/indexing/plugins/profile.ts @@ -1,12 +1,11 @@ import { AtUri } from '@atproto/syntax' import { CID } from 'multiformats/cid' -import * as Profile from '../../../lexicon/types/app/bsky/actor/profile' -import * as lex from '../../../lexicon/lexicons' -import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' +import * as Profile from '../../../../lexicon/types/app/bsky/actor/profile' +import * as lex from '../../../../lexicon/lexicons' +import { DatabaseSchema, DatabaseSchemaType } from '../../db/database-schema' import RecordProcessor from '../processor' -import { PrimaryDatabase } from '../../../db' -import { BackgroundQueue } from '../../../background' -import { NotificationServer } from '../../../notifications' +import { Database } from '../../db' +import { BackgroundQueue } from '../../background' const lexId = lex.ids.AppBskyActorProfile type IndexedProfile = DatabaseSchemaType['profile'] @@ -64,11 +63,10 @@ const notifsForDelete = () => { export type PluginType = RecordProcessor export const makePlugin = ( - db: PrimaryDatabase, - backgroundQueue: BackgroundQueue, - notifServer?: NotificationServer, + db: Database, + background: BackgroundQueue, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, notifServer, { + return new RecordProcessor(db, background, { lexId, insertFn, findDuplicate, diff --git a/packages/bsky/src/services/indexing/plugins/repost.ts b/packages/bsky/src/data-plane/server/indexing/plugins/repost.ts similarity index 85% rename from packages/bsky/src/services/indexing/plugins/repost.ts rename to packages/bsky/src/data-plane/server/indexing/plugins/repost.ts index ea8d517dc52..ec2e7754fb0 100644 --- a/packages/bsky/src/services/indexing/plugins/repost.ts +++ b/packages/bsky/src/data-plane/server/indexing/plugins/repost.ts @@ -1,14 +1,13 @@ import { Selectable } from 'kysely' import { CID } from 'multiformats/cid' import { AtUri, normalizeDatetimeAlways } from '@atproto/syntax' -import * as Repost from '../../../lexicon/types/app/bsky/feed/repost' -import * as lex from '../../../lexicon/lexicons' -import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' +import * as Repost from '../../../../lexicon/types/app/bsky/feed/repost' +import * as lex from '../../../../lexicon/lexicons' import RecordProcessor from '../processor' -import { PrimaryDatabase } from '../../../db' -import { countAll, excluded } from '../../../db/util' -import { BackgroundQueue } from '../../../background' -import { NotificationServer } from '../../../notifications' +import { Database } from '../../db' +import { countAll, excluded } from '../../db/util' +import { DatabaseSchema, DatabaseSchemaType } from '../../db/database-schema' +import { BackgroundQueue } from '../../background' const lexId = lex.ids.AppBskyFeedRepost type IndexedRepost = Selectable @@ -134,11 +133,10 @@ const updateAggregates = async (db: DatabaseSchema, repost: IndexedRepost) => { export type PluginType = RecordProcessor export const makePlugin = ( - db: PrimaryDatabase, - backgroundQueue: BackgroundQueue, - notifServer?: NotificationServer, + db: Database, + background: BackgroundQueue, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, notifServer, { + return new RecordProcessor(db, background, { lexId, insertFn, findDuplicate, diff --git a/packages/bsky/src/services/indexing/plugins/thread-gate.ts b/packages/bsky/src/data-plane/server/indexing/plugins/thread-gate.ts similarity index 79% rename from packages/bsky/src/services/indexing/plugins/thread-gate.ts rename to packages/bsky/src/data-plane/server/indexing/plugins/thread-gate.ts index 9a58547f2da..0402fe8289f 100644 --- a/packages/bsky/src/services/indexing/plugins/thread-gate.ts +++ b/packages/bsky/src/data-plane/server/indexing/plugins/thread-gate.ts @@ -1,13 +1,12 @@ import { AtUri, normalizeDatetimeAlways } from '@atproto/syntax' import { InvalidRequestError } from '@atproto/xrpc-server' import { CID } from 'multiformats/cid' -import * as Threadgate from '../../../lexicon/types/app/bsky/feed/threadgate' -import * as lex from '../../../lexicon/lexicons' -import { DatabaseSchema, DatabaseSchemaType } from '../../../db/database-schema' +import * as Threadgate from '../../../../lexicon/types/app/bsky/feed/threadgate' +import * as lex from '../../../../lexicon/lexicons' +import { DatabaseSchema, DatabaseSchemaType } from '../../db/database-schema' +import { Database } from '../../db' import RecordProcessor from '../processor' -import { PrimaryDatabase } from '../../../db' -import { BackgroundQueue } from '../../../background' -import { NotificationServer } from '../../../notifications' +import { BackgroundQueue } from '../../background' const lexId = lex.ids.AppBskyFeedThreadgate type IndexedGate = DatabaseSchemaType['thread_gate'] @@ -77,11 +76,10 @@ const notifsForDelete = () => { export type PluginType = RecordProcessor export const makePlugin = ( - db: PrimaryDatabase, - backgroundQueue: BackgroundQueue, - notifServer?: NotificationServer, + db: Database, + background: BackgroundQueue, ): PluginType => { - return new RecordProcessor(db, backgroundQueue, notifServer, { + return new RecordProcessor(db, background, { lexId, insertFn, findDuplicate, diff --git a/packages/bsky/src/services/indexing/processor.ts b/packages/bsky/src/data-plane/server/indexing/processor.ts similarity index 80% rename from packages/bsky/src/services/indexing/processor.ts rename to packages/bsky/src/data-plane/server/indexing/processor.ts index 0dad405b9ef..77a8fbdf09f 100644 --- a/packages/bsky/src/services/indexing/processor.ts +++ b/packages/bsky/src/data-plane/server/indexing/processor.ts @@ -1,15 +1,13 @@ import { Insertable } from 'kysely' import { CID } from 'multiformats/cid' import { AtUri } from '@atproto/syntax' -import { jsonStringToLex, stringifyLex } from '@atproto/lexicon' -import DatabaseSchema from '../../db/database-schema' -import { lexicons } from '../../lexicon/lexicons' -import { Notification } from '../../db/tables/notification' import { chunkArray } from '@atproto/common' -import { PrimaryDatabase } from '../../db' -import { BackgroundQueue } from '../../background' -import { NotificationServer } from '../../notifications' -import { dbLogger } from '../../logger' +import { jsonStringToLex, stringifyLex } from '@atproto/lexicon' +import { lexicons } from '../../../lexicon/lexicons' +import { Database } from '../db' +import DatabaseSchema from '../db/database-schema' +import { Notification } from '../db/tables/notification' +import { BackgroundQueue } from '../background' // @NOTE re: insertions and deletions. Due to how record updates are handled, // (insertFn) should have the same effect as (insertFn -> deleteFn -> insertFn). @@ -42,9 +40,8 @@ export class RecordProcessor { collection: string db: DatabaseSchema constructor( - private appDb: PrimaryDatabase, - private backgroundQueue: BackgroundQueue, - private notifServer: NotificationServer | undefined, + private appDb: Database, + private background: BackgroundQueue, private params: RecordProcessorParams, ) { this.db = appDb.db @@ -228,8 +225,7 @@ export class RecordProcessor { async handleNotifs(op: { deleted?: S; inserted?: S }) { let notifs: Notif[] = [] - const runOnCommit: ((db: PrimaryDatabase) => Promise)[] = [] - const sendOnCommit: (() => Promise)[] = [] + const runOnCommit: ((db: Database) => Promise)[] = [] if (op.deleted) { const forDelete = this.params.notifsForDelete( op.deleted, @@ -253,37 +249,10 @@ export class RecordProcessor { runOnCommit.push(async (db) => { await db.db.insertInto('notification').values(chunk).execute() }) - if (this.notifServer) { - const notifServer = this.notifServer - sendOnCommit.push(async () => { - try { - const preparedNotifs = await notifServer.prepareNotifications(chunk) - await notifServer.processNotifications(preparedNotifs) - } catch (error) { - dbLogger.error({ error }, 'error sending push notifications') - } - }) - } } - if (runOnCommit.length) { - // Need to ensure notif deletion always happens before creation, otherwise delete may clobber in a race. - this.appDb.onCommit(() => { - this.backgroundQueue.add(async (db) => { - for (const fn of runOnCommit) { - await fn(db) - } - }) - }) - } - if (sendOnCommit.length) { - // Need to ensure notif deletion always happens before creation, otherwise delete may clobber in a race. - this.appDb.onCommit(() => { - this.backgroundQueue.add(async () => { - for (const fn of sendOnCommit) { - await fn() - } - }) - }) + // Need to ensure notif deletion always happens before creation, otherwise delete may clobber in a race. + for (const fn of runOnCommit) { + await fn(this.appDb) // these could be backgrounded } } @@ -291,7 +260,7 @@ export class RecordProcessor { const { updateAggregates } = this.params if (!updateAggregates) return this.appDb.onCommit(() => { - this.backgroundQueue.add((db) => updateAggregates(db.db, indexed)) + this.background.add((db) => updateAggregates(db.db, indexed)) }) } } diff --git a/packages/bsky/src/data-plane/server/routes/blocks.ts b/packages/bsky/src/data-plane/server/routes/blocks.ts new file mode 100644 index 00000000000..54ec900b280 --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/blocks.ts @@ -0,0 +1,121 @@ +import { ServiceImpl } from '@connectrpc/connect' +import { Service } from '../../../proto/bsky_connect' +import { Database } from '../db' +import { TimeCidKeyset, paginate } from '../db/pagination' + +export default (db: Database): Partial> => ({ + async getBidirectionalBlock(req) { + const { actorDid, targetDid } = req + const res = await db.db + .selectFrom('actor_block') + .where((qb) => + qb + .where('actor_block.creator', '=', actorDid) + .where('actor_block.subjectDid', '=', targetDid), + ) + .orWhere((qb) => + qb + .where('actor_block.creator', '=', targetDid) + .where('actor_block.subjectDid', '=', actorDid), + ) + .limit(1) + .selectAll() + .executeTakeFirst() + + return { + blockUri: res?.uri, + } + }, + + async getBlocks(req) { + const { actorDid, cursor, limit } = req + const { ref } = db.db.dynamic + + let builder = db.db + .selectFrom('actor_block') + .where('actor_block.creator', '=', actorDid) + .selectAll() + + const keyset = new TimeCidKeyset( + ref('actor_block.sortAt'), + ref('actor_block.cid'), + ) + builder = paginate(builder, { + limit, + cursor, + keyset, + }) + + const blocks = await builder.execute() + return { + blockUris: blocks.map((b) => b.uri), + cursor: keyset.packFromResult(blocks), + } + }, + + async getBidirectionalBlockViaList(req) { + const { actorDid, targetDid } = req + const res = await db.db + .selectFrom('list_block') + .innerJoin('list_item', 'list_item.listUri', 'list_block.subjectUri') + .where((qb) => + qb + .where('list_block.creator', '=', actorDid) + .where('list_item.subjectDid', '=', targetDid), + ) + .orWhere((qb) => + qb + .where('list_block.creator', '=', targetDid) + .where('list_item.subjectDid', '=', actorDid), + ) + .limit(1) + .selectAll('list_block') + .executeTakeFirst() + + return { + listUri: res?.subjectUri, + } + }, + + async getBlocklistSubscription(req) { + const { actorDid, listUri } = req + const res = await db.db + .selectFrom('list_block') + .where('creator', '=', actorDid) + .where('subjectUri', '=', listUri) + .selectAll() + .limit(1) + .executeTakeFirst() + return { + listblockUri: res?.uri, + } + }, + + async getBlocklistSubscriptions(req) { + const { actorDid, limit, cursor } = req + const { ref } = db.db.dynamic + let builder = db.db + .selectFrom('list') + .whereExists( + db.db + .selectFrom('list_block') + .where('list_block.creator', '=', actorDid) + .whereRef('list_block.subjectUri', '=', ref('list.uri')) + .selectAll(), + ) + .selectAll('list') + + const keyset = new TimeCidKeyset(ref('list.createdAt'), ref('list.cid')) + builder = paginate(builder, { + limit, + cursor, + keyset, + }) + const lists = await builder.execute() + + return { + listUris: lists.map((l) => l.uri), + cursor: keyset.packFromResult(lists), + } + }, +}) diff --git a/packages/bsky/src/data-plane/server/routes/feed-gens.ts b/packages/bsky/src/data-plane/server/routes/feed-gens.ts new file mode 100644 index 00000000000..129229f923f --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/feed-gens.ts @@ -0,0 +1,70 @@ +import { ServiceImpl } from '@connectrpc/connect' +import { Service } from '../../../proto/bsky_connect' +import { Database } from '../db' +import { TimeCidKeyset, paginate } from '../db/pagination' + +export default (db: Database): Partial> => ({ + async getActorFeeds(req) { + const { actorDid, limit, cursor } = req + + const { ref } = db.db.dynamic + let builder = db.db + .selectFrom('feed_generator') + .selectAll() + .where('feed_generator.creator', '=', actorDid) + + const keyset = new TimeCidKeyset( + ref('feed_generator.createdAt'), + ref('feed_generator.cid'), + ) + builder = paginate(builder, { + limit, + cursor, + keyset, + }) + const feeds = await builder.execute() + + return { + uris: feeds.map((f) => f.uri), + cursor: keyset.packFromResult(feeds), + } + }, + + async getSuggestedFeeds(req) { + const feeds = await db.db + .selectFrom('suggested_feed') + .orderBy('suggested_feed.order', 'asc') + .if(!!req.cursor, (q) => q.where('order', '>', parseInt(req.cursor, 10))) + .limit(req.limit || 50) + .selectAll() + .execute() + return { + uris: feeds.map((f) => f.uri), + cursor: feeds.at(-1)?.order.toString(), + } + }, + + async searchFeedGenerators(req) { + const { ref } = db.db.dynamic + const limit = req.limit + const query = req.query.trim() + let builder = db.db + .selectFrom('feed_generator') + .if(!!query, (q) => q.where('displayName', 'ilike', `%${query}%`)) + .selectAll() + const keyset = new TimeCidKeyset( + ref('feed_generator.createdAt'), + ref('feed_generator.cid'), + ) + builder = paginate(builder, { limit, keyset }) + const feeds = await builder.execute() + return { + uris: feeds.map((f) => f.uri), + cursor: keyset.packFromResult(feeds), + } + }, + + async getFeedGeneratorStatus() { + throw new Error('unimplemented') + }, +}) diff --git a/packages/bsky/src/data-plane/server/routes/feeds.ts b/packages/bsky/src/data-plane/server/routes/feeds.ts new file mode 100644 index 00000000000..3cb56d57e3f --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/feeds.ts @@ -0,0 +1,148 @@ +import { ServiceImpl } from '@connectrpc/connect' +import { Service } from '../../../proto/bsky_connect' +import { Database } from '../db' +import { TimeCidKeyset, paginate } from '../db/pagination' +import { FeedType } from '../../../proto/bsky_pb' + +export default (db: Database): Partial> => ({ + async getAuthorFeed(req) { + const { actorDid, limit, cursor, feedType } = req + const { ref } = db.db.dynamic + + // defaults to posts, reposts, and replies + let builder = db.db + .selectFrom('feed_item') + .innerJoin('post', 'post.uri', 'feed_item.postUri') + .selectAll('feed_item') + .where('originatorDid', '=', actorDid) + + if (feedType === FeedType.POSTS_WITH_MEDIA) { + builder = builder + // only your own posts + .where('type', '=', 'post') + // only posts with media + .whereExists((qb) => + qb + .selectFrom('post_embed_image') + .select('post_embed_image.postUri') + .whereRef('post_embed_image.postUri', '=', 'feed_item.postUri'), + ) + } else if (feedType === FeedType.POSTS_NO_REPLIES) { + builder = builder.where((qb) => + qb.where('post.replyParent', 'is', null).orWhere('type', '=', 'repost'), + ) + } else if (feedType === FeedType.POSTS_AND_AUTHOR_THREADS) { + builder = builder.where((qb) => + qb + .where('type', '=', 'repost') + .orWhere('post.replyParent', 'is', null) + .orWhere('post.replyRoot', 'like', `at://${actorDid}/%`), + ) + } + + const keyset = new TimeCidKeyset( + ref('feed_item.sortAt'), + ref('feed_item.cid'), + ) + + builder = paginate(builder, { + limit, + cursor, + keyset, + }) + + const feedItems = await builder.execute() + + return { + items: feedItems.map(feedItemFromRow), + cursor: keyset.packFromResult(feedItems), + } + }, + + async getTimeline(req) { + const { actorDid, limit, cursor } = req + const { ref } = db.db.dynamic + + const keyset = new TimeCidKeyset( + ref('feed_item.sortAt'), + ref('feed_item.cid'), + ) + + let followQb = db.db + .selectFrom('feed_item') + .innerJoin('follow', 'follow.subjectDid', 'feed_item.originatorDid') + .where('follow.creator', '=', actorDid) + .selectAll('feed_item') + + followQb = paginate(followQb, { + limit, + cursor, + keyset, + tryIndex: true, + }) + + let selfQb = db.db + .selectFrom('feed_item') + .where('feed_item.originatorDid', '=', actorDid) + .selectAll('feed_item') + + selfQb = paginate(selfQb, { + limit: Math.min(limit, 10), + cursor, + keyset, + tryIndex: true, + }) + + const [followRes, selfRes] = await Promise.all([ + followQb.execute(), + selfQb.execute(), + ]) + + const feedItems = [...followRes, ...selfRes] + .sort((a, b) => { + if (a.sortAt > b.sortAt) return -1 + if (a.sortAt < b.sortAt) return 1 + return a.cid > b.cid ? -1 : 1 + }) + .slice(0, limit) + + return { + items: feedItems.map(feedItemFromRow), + cursor: keyset.packFromResult(feedItems), + } + }, + + async getListFeed(req) { + const { listUri, cursor, limit } = req + const { ref } = db.db.dynamic + + let builder = db.db + .selectFrom('post') + .selectAll('post') + .innerJoin('list_item', 'list_item.subjectDid', 'post.creator') + .where('list_item.listUri', '=', listUri) + + const keyset = new TimeCidKeyset(ref('post.sortAt'), ref('post.cid')) + builder = paginate(builder, { + limit, + cursor, + keyset, + tryIndex: true, + }) + const feedItems = await builder.execute() + + return { + items: feedItems.map((item) => ({ uri: item.uri, cid: item.cid })), + cursor: keyset.packFromResult(feedItems), + } + }, +}) + +// @NOTE does not support additional fields in the protos specific to author feeds +// and timelines. at the time of writing, hydration/view implementations do not rely on them. +const feedItemFromRow = (row: { postUri: string; uri: string }) => { + return { + uri: row.postUri, + repost: row.uri === row.postUri ? undefined : row.uri, + } +} diff --git a/packages/bsky/src/data-plane/server/routes/follows.ts b/packages/bsky/src/data-plane/server/routes/follows.ts new file mode 100644 index 00000000000..1380fa281d6 --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/follows.ts @@ -0,0 +1,95 @@ +import { keyBy } from '@atproto/common' +import { ServiceImpl } from '@connectrpc/connect' +import { Service } from '../../../proto/bsky_connect' +import { Database } from '../db' +import { TimeCidKeyset, paginate } from '../db/pagination' + +export default (db: Database): Partial> => ({ + async getActorFollowsActors(req) { + const { actorDid, targetDids } = req + if (targetDids.length < 1) { + return { uris: [] } + } + const res = await db.db + .selectFrom('follow') + .where('follow.creator', '=', actorDid) + .where('follow.subjectDid', 'in', targetDids) + .selectAll() + .execute() + const bySubject = keyBy(res, 'subjectDid') + const uris = targetDids.map((did) => bySubject[did]?.uri ?? '') + return { + uris, + } + }, + async getFollowers(req) { + const { actorDid, limit, cursor } = req + const { ref } = db.db.dynamic + let followersReq = db.db + .selectFrom('follow') + .where('follow.subjectDid', '=', actorDid) + .innerJoin('actor as creator', 'creator.did', 'follow.creator') + .selectAll('creator') + .select([ + 'follow.uri as uri', + 'follow.cid as cid', + 'follow.creator as creatorDid', + 'follow.subjectDid as subjectDid', + 'follow.sortAt as sortAt', + ]) + + const keyset = new TimeCidKeyset(ref('follow.sortAt'), ref('follow.cid')) + followersReq = paginate(followersReq, { + limit, + cursor, + keyset, + tryIndex: true, + }) + + const followers = await followersReq.execute() + return { + followers: followers.map((f) => ({ + uri: f.uri, + actorDid: f.creatorDid, + subjectDid: f.subjectDid, + })), + cursor: keyset.packFromResult(followers), + } + }, + async getFollows(req) { + const { actorDid, limit, cursor } = req + const { ref } = db.db.dynamic + + let followsReq = db.db + .selectFrom('follow') + .where('follow.creator', '=', actorDid) + .innerJoin('actor as subject', 'subject.did', 'follow.subjectDid') + .selectAll('subject') + .select([ + 'follow.uri as uri', + 'follow.cid as cid', + 'follow.creator as creatorDid', + 'follow.subjectDid as subjectDid', + 'follow.sortAt as sortAt', + ]) + + const keyset = new TimeCidKeyset(ref('follow.sortAt'), ref('follow.cid')) + followsReq = paginate(followsReq, { + limit, + cursor, + keyset, + tryIndex: true, + }) + + const follows = await followsReq.execute() + + return { + follows: follows.map((f) => ({ + uri: f.uri, + actorDid: f.creatorDid, + subjectDid: f.subjectDid, + })), + cursor: keyset.packFromResult(follows), + } + }, +}) diff --git a/packages/bsky/src/data-plane/server/routes/identity.ts b/packages/bsky/src/data-plane/server/routes/identity.ts new file mode 100644 index 00000000000..fb8dffc096c --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/identity.ts @@ -0,0 +1,59 @@ +import { Code, ConnectError, ServiceImpl } from '@connectrpc/connect' +import { Service } from '../../../proto/bsky_connect' +import { Database } from '../db' +import { DidDocument, IdResolver, getDid, getHandle } from '@atproto/identity' +import { Timestamp } from '@bufbuild/protobuf' + +export default ( + _db: Database, + idResolver: IdResolver, +): Partial> => ({ + async getIdentityByDid(req) { + const doc = await idResolver.did.resolve(req.did) + if (!doc) { + throw new ConnectError('identity not found', Code.NotFound) + } + return getResultFromDoc(doc) + }, + + async getIdentityByHandle(req) { + const did = await idResolver.handle.resolve(req.handle) + if (!did) { + throw new ConnectError('identity not found', Code.NotFound) + } + const doc = await idResolver.did.resolve(did) + if (!doc || did !== getDid(doc)) { + throw new ConnectError('identity not found', Code.NotFound) + } + return getResultFromDoc(doc) + }, +}) + +const getResultFromDoc = (doc: DidDocument) => { + const keys: Record = {} + doc.verificationMethod?.forEach((method) => { + const id = method.id.split('#').at(1) + if (!id) return + keys[id] = { + Type: method.type, + PublicKeyMultibase: method.publicKeyMultibase || '', + } + }) + const services: Record = {} + doc.service?.forEach((service) => { + const id = service.id.split('#').at(1) + if (!id) return + if (typeof service.serviceEndpoint !== 'string') return + services[id] = { + Type: service.type, + URL: service.serviceEndpoint, + } + }) + return { + did: getDid(doc), + handle: getHandle(doc), + keys: Buffer.from(JSON.stringify(keys)), + services: Buffer.from(JSON.stringify(services)), + updated: Timestamp.fromDate(new Date()), + } +} diff --git a/packages/bsky/src/data-plane/server/routes/index.ts b/packages/bsky/src/data-plane/server/routes/index.ts new file mode 100644 index 00000000000..6169a48a28d --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/index.ts @@ -0,0 +1,55 @@ +import { ConnectRouter } from '@connectrpc/connect' +import { IdResolver } from '@atproto/identity' +import { Service } from '../../../proto/bsky_connect' +import blocks from './blocks' +import feedGens from './feed-gens' +import feeds from './feeds' +import follows from './follows' +import identity from './identity' +import interactions from './interactions' +import labels from './labels' +import likes from './likes' +import lists from './lists' +import moderation from './moderation' +import mutes from './mutes' +import notifs from './notifs' +import posts from './posts' +import profile from './profile' +import records from './records' +import relationships from './relationships' +import reposts from './reposts' +import search from './search' +import suggestions from './suggestions' +import sync from './sync' +import threads from './threads' +import { Database } from '../db' + +export default (db: Database, idResolver: IdResolver) => + (router: ConnectRouter) => + router.service(Service, { + ...blocks(db), + ...feedGens(db), + ...feeds(db), + ...follows(db), + ...identity(db, idResolver), + ...interactions(db), + ...labels(db), + ...likes(db), + ...lists(db), + ...moderation(db), + ...mutes(db), + ...notifs(db), + ...posts(db), + ...profile(db), + ...records(db), + ...relationships(db), + ...reposts(db), + ...search(db), + ...suggestions(db), + ...sync(db), + ...threads(db), + + async ping() { + return {} + }, + }) diff --git a/packages/bsky/src/data-plane/server/routes/interactions.ts b/packages/bsky/src/data-plane/server/routes/interactions.ts new file mode 100644 index 00000000000..73e46372a55 --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/interactions.ts @@ -0,0 +1,40 @@ +import { keyBy } from '@atproto/common' +import { ServiceImpl } from '@connectrpc/connect' +import { Service } from '../../../proto/bsky_connect' +import { Database } from '../db' + +export default (db: Database): Partial> => ({ + async getInteractionCounts(req) { + const uris = req.refs.map((ref) => ref.uri) + if (uris.length === 0) { + return { likes: [], replies: [], reposts: [] } + } + const res = await db.db + .selectFrom('post_agg') + .where('uri', 'in', uris) + .selectAll() + .execute() + const byUri = keyBy(res, 'uri') + return { + likes: uris.map((uri) => byUri[uri]?.likeCount ?? 0), + replies: uris.map((uri) => byUri[uri]?.replyCount ?? 0), + reposts: uris.map((uri) => byUri[uri]?.repostCount ?? 0), + } + }, + async getCountsForUsers(req) { + if (req.dids.length === 0) { + return { followers: [], following: [], posts: [] } + } + const res = await db.db + .selectFrom('profile_agg') + .selectAll() + .where('did', 'in', req.dids) + .execute() + const byDid = keyBy(res, 'did') + return { + followers: req.dids.map((uri) => byDid[uri]?.followersCount ?? 0), + following: req.dids.map((uri) => byDid[uri]?.followsCount ?? 0), + posts: req.dids.map((uri) => byDid[uri]?.postsCount ?? 0), + } + }, +}) diff --git a/packages/bsky/src/data-plane/server/routes/labels.ts b/packages/bsky/src/data-plane/server/routes/labels.ts new file mode 100644 index 00000000000..dd58387f579 --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/labels.ts @@ -0,0 +1,28 @@ +import * as ui8 from 'uint8arrays' +import { ServiceImpl } from '@connectrpc/connect' +import { Service } from '../../../proto/bsky_connect' +import { Database } from '../db' + +export default (db: Database): Partial> => ({ + async getLabels(req) { + const { subjects, issuers } = req + if (subjects.length === 0 || issuers.length === 0) { + return { records: [] } + } + const res = await db.db + .selectFrom('label') + .where('uri', 'in', subjects) + .where('src', 'in', issuers) + .selectAll() + .execute() + + const labels = res.map((l) => { + const formatted = { + ...l, + cid: l.cid === '' ? undefined : l.cid, + } + return ui8.fromString(JSON.stringify(formatted), 'utf8') + }) + return { labels } + }, +}) diff --git a/packages/bsky/src/data-plane/server/routes/likes.ts b/packages/bsky/src/data-plane/server/routes/likes.ts new file mode 100644 index 00000000000..7f7ace202b7 --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/likes.ts @@ -0,0 +1,86 @@ +import { ServiceImpl } from '@connectrpc/connect' +import { Service } from '../../../proto/bsky_connect' +import { Database } from '../db' +import { TimeCidKeyset, paginate } from '../db/pagination' +import { keyBy } from '@atproto/common' + +export default (db: Database): Partial> => ({ + async getLikesBySubject(req) { + const { subject, cursor, limit } = req + const { ref } = db.db.dynamic + + if (!subject?.uri) { + return { uris: [] } + } + + // @NOTE ignoring subject.cid + let builder = db.db + .selectFrom('like') + .where('like.subject', '=', subject?.uri) + .selectAll('like') + + const keyset = new TimeCidKeyset(ref('like.sortAt'), ref('like.cid')) + builder = paginate(builder, { + limit, + cursor, + keyset, + }) + + const likes = await builder.execute() + + return { + uris: likes.map((l) => l.uri), + cursor: keyset.packFromResult(likes), + } + }, + + async getLikesByActorAndSubjects(req) { + const { actorDid, refs } = req + if (refs.length === 0) { + return { uris: [] } + } + // @NOTE ignoring ref.cid + const res = await db.db + .selectFrom('like') + .where('creator', '=', actorDid) + .where( + 'subject', + 'in', + refs.map(({ uri }) => uri), + ) + .selectAll() + .execute() + const bySubject = keyBy(res, 'subject') + // @TODO handling undefineds properly, or do we need to turn them into empty strings? + const uris = refs.map(({ uri }) => bySubject[uri]?.uri) + return { uris } + }, + + async getActorLikes(req) { + const { actorDid, limit, cursor } = req + const { ref } = db.db.dynamic + + let builder = db.db + .selectFrom('like') + .where('like.creator', '=', actorDid) + .selectAll() + + const keyset = new TimeCidKeyset(ref('like.sortAt'), ref('like.cid')) + + builder = paginate(builder, { + limit, + cursor, + keyset, + }) + + const likes = await builder.execute() + + return { + likes: likes.map((l) => ({ + uri: l.uri, + subject: l.subject, + })), + cursor: keyset.packFromResult(likes), + } + }, +}) diff --git a/packages/bsky/src/data-plane/server/routes/lists.ts b/packages/bsky/src/data-plane/server/routes/lists.ts new file mode 100644 index 00000000000..d757b1d39e4 --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/lists.ts @@ -0,0 +1,88 @@ +import { keyBy } from '@atproto/common' +import { ServiceImpl } from '@connectrpc/connect' +import { Service } from '../../../proto/bsky_connect' +import { Database } from '../db' +import { countAll } from '../db/util' +import { TimeCidKeyset, paginate } from '../db/pagination' + +export default (db: Database): Partial> => ({ + async getActorLists(req) { + const { actorDid, cursor, limit } = req + const { ref } = db.db.dynamic + let builder = db.db + .selectFrom('list') + .where('creator', '=', actorDid) + .selectAll() + const keyset = new TimeCidKeyset(ref('list.sortAt'), ref('list.cid')) + builder = paginate(builder, { + limit, + cursor, + keyset, + tryIndex: true, + }) + const lists = await builder.execute() + return { + listUris: lists.map((item) => item.uri), + cursor: keyset.packFromResult(lists), + } + }, + + async getListMembers(req) { + const { listUri, cursor, limit } = req + const { ref } = db.db.dynamic + let builder = db.db + .selectFrom('list_item') + .where('listUri', '=', listUri) + .selectAll() + + const keyset = new TimeCidKeyset( + ref('list_item.sortAt'), + ref('list_item.cid'), + ) + + builder = paginate(builder, { + limit, + cursor, + keyset, + tryIndex: true, + }) + + const listItems = await builder.execute() + return { + listitems: listItems.map((item) => ({ + uri: item.uri, + did: item.subjectDid, + })), + cursor: keyset.packFromResult(listItems), + } + }, + + async getListMembership(req) { + const { actorDid, listUris } = req + if (listUris.length === 0) { + return { listitemUris: [] } + } + const res = await db.db + .selectFrom('list_item') + .where('subjectDid', '=', actorDid) + .where('listUri', 'in', listUris) + .selectAll() + .execute() + const byListUri = keyBy(res, 'listUri') + const listitemUris = listUris.map((uri) => byListUri[uri]?.uri ?? '') + return { + listitemUris, + } + }, + + async getListCount(req) { + const res = await db.db + .selectFrom('list_item') + .select(countAll.as('count')) + .where('list_item.listUri', '=', req.listUri) + .executeTakeFirst() + return { + count: res?.count, + } + }, +}) diff --git a/packages/bsky/src/data-plane/server/routes/moderation.ts b/packages/bsky/src/data-plane/server/routes/moderation.ts new file mode 100644 index 00000000000..d59360847fe --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/moderation.ts @@ -0,0 +1,102 @@ +import { ServiceImpl } from '@connectrpc/connect' +import { Service } from '../../../proto/bsky_connect' +import { Database } from '../db' + +export default (db: Database): Partial> => ({ + async getActorTakedown(req) { + const { did } = req + const res = await db.db + .selectFrom('actor') + .where('did', '=', did) + .select('takedownRef') + .executeTakeFirst() + return { + takenDown: !!res?.takedownRef, + takedownRef: res?.takedownRef || undefined, + } + }, + + async getBlobTakedown(req) { + const { did, cid } = req + const res = await db.db + .selectFrom('blob_takedown') + .where('did', '=', did) + .where('cid', '=', cid) + .select('takedownRef') + .executeTakeFirst() + return { + takenDown: !!res, + takedownRef: res?.takedownRef || undefined, + } + }, + + async getRecordTakedown(req) { + const { recordUri } = req + const res = await db.db + .selectFrom('record') + .where('uri', '=', recordUri) + .select('takedownRef') + .executeTakeFirst() + return { + takenDown: !!res?.takedownRef, + takedownRef: res?.takedownRef || undefined, + } + }, + + async takedownActor(req) { + const { did, ref } = req + await db.db + .updateTable('actor') + .set({ takedownRef: ref || 'TAKEDOWN' }) + .where('did', '=', did) + .execute() + }, + + async takedownBlob(req) { + const { did, cid, ref } = req + await db.db + .insertInto('blob_takedown') + .values({ + did, + cid, + takedownRef: ref || 'TAKEDOWN', + }) + .execute() + }, + + async takedownRecord(req) { + const { recordUri, ref } = req + await db.db + .updateTable('record') + .set({ takedownRef: ref || 'TAKEDOWN' }) + .where('uri', '=', recordUri) + .execute() + }, + + async untakedownActor(req) { + const { did } = req + await db.db + .updateTable('actor') + .set({ takedownRef: null }) + .where('did', '=', did) + .execute() + }, + + async untakedownBlob(req) { + const { did, cid } = req + await db.db + .deleteFrom('blob_takedown') + .where('did', '=', did) + .where('cid', '=', cid) + .executeTakeFirst() + }, + + async untakedownRecord(req) { + const { recordUri } = req + await db.db + .updateTable('record') + .set({ takedownRef: null }) + .where('uri', '=', recordUri) + .execute() + }, +}) diff --git a/packages/bsky/src/data-plane/server/routes/mutes.ts b/packages/bsky/src/data-plane/server/routes/mutes.ts new file mode 100644 index 00000000000..387e0f4d904 --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/mutes.ts @@ -0,0 +1,172 @@ +import assert from 'assert' +import { ServiceImpl } from '@connectrpc/connect' +import { AtUri } from '@atproto/syntax' +import { ids } from '../../../lexicon/lexicons' +import { Service } from '../../../proto/bsky_connect' +import { Database } from '../db' +import { CreatedAtDidKeyset, TimeCidKeyset, paginate } from '../db/pagination' + +export default (db: Database): Partial> => ({ + async getActorMutesActor(req) { + const { actorDid, targetDid } = req + const res = await db.db + .selectFrom('mute') + .selectAll() + .where('mutedByDid', '=', actorDid) + .where('subjectDid', '=', targetDid) + .executeTakeFirst() + return { + muted: !!res, + } + }, + + async getMutes(req) { + const { actorDid, limit, cursor } = req + const { ref } = db.db.dynamic + + let builder = db.db + .selectFrom('mute') + .innerJoin('actor', 'actor.did', 'mute.subjectDid') + .where('mute.mutedByDid', '=', actorDid) + .selectAll('actor') + .select('mute.createdAt as createdAt') + + const keyset = new CreatedAtDidKeyset( + ref('mute.createdAt'), + ref('mute.subjectDid'), + ) + builder = paginate(builder, { + limit, + cursor, + keyset, + }) + + const mutes = await builder.execute() + + return { + dids: mutes.map((m) => m.did), + cursor: keyset.packFromResult(mutes), + } + }, + + async getActorMutesActorViaList(req) { + const { actorDid, targetDid } = req + const res = await db.db + .selectFrom('list_mute') + .innerJoin('list_item', 'list_item.listUri', 'list_mute.listUri') + .where('list_mute.mutedByDid', '=', actorDid) + .where('list_item.subjectDid', '=', targetDid) + .select('list_mute.listUri') + .limit(1) + .executeTakeFirst() + return { + listUri: res?.listUri, + } + }, + + async getMutelistSubscription(req) { + const { actorDid, listUri } = req + const res = await db.db + .selectFrom('list_mute') + .where('mutedByDid', '=', actorDid) + .where('listUri', '=', listUri) + .selectAll() + .limit(1) + .executeTakeFirst() + return { + subscribed: !!res, + } + }, + + async getMutelistSubscriptions(req) { + const { actorDid, limit, cursor } = req + const { ref } = db.db.dynamic + let builder = db.db + .selectFrom('list') + .whereExists( + db.db + .selectFrom('list_mute') + .where('list_mute.mutedByDid', '=', actorDid) + .whereRef('list_mute.listUri', '=', ref('list.uri')) + .selectAll(), + ) + .selectAll('list') + + const keyset = new TimeCidKeyset(ref('list.createdAt'), ref('list.cid')) + builder = paginate(builder, { + limit, + cursor, + keyset, + }) + const lists = await builder.execute() + + return { + listUris: lists.map((l) => l.uri), + cursor: keyset.packFromResult(lists), + } + }, + + async createActorMute(req) { + const { actorDid, subjectDid } = req + assert(actorDid !== subjectDid, 'cannot mute yourself') // @TODO pass message through in http error + await db.db + .insertInto('mute') + .values({ + subjectDid, + mutedByDid: actorDid, + createdAt: new Date().toISOString(), + }) + .onConflict((oc) => oc.doNothing()) + .execute() + }, + + async deleteActorMute(req) { + const { actorDid, subjectDid } = req + assert(actorDid !== subjectDid, 'cannot mute yourself') + await db.db + .deleteFrom('mute') + .where('subjectDid', '=', subjectDid) + .where('mutedByDid', '=', actorDid) + .execute() + }, + + async clearActorMutes(req) { + const { actorDid } = req + await db.db.deleteFrom('mute').where('mutedByDid', '=', actorDid).execute() + }, + + async createActorMutelistSubscription(req) { + const { actorDid, subjectUri } = req + assert(isListUri(subjectUri), 'must mute a list') + await db.db + .insertInto('list_mute') + .values({ + listUri: subjectUri, + mutedByDid: actorDid, + createdAt: new Date().toISOString(), + }) + .onConflict((oc) => oc.doNothing()) + .execute() + }, + + async deleteActorMutelistSubscription(req) { + const { actorDid, subjectUri } = req + assert(isListUri(subjectUri), 'must mute a list') + await db.db + .deleteFrom('list_mute') + .where('listUri', '=', subjectUri) + .where('mutedByDid', '=', actorDid) + .execute() + }, + + async clearActorMutelistSubscriptions(req) { + const { actorDid } = req + await db.db + .deleteFrom('list_mute') + .where('mutedByDid', '=', actorDid) + .execute() + }, +}) + +const isListUri = (uri: string) => + new AtUri(uri).collection === ids.AppBskyGraphList diff --git a/packages/bsky/src/data-plane/server/routes/notifs.ts b/packages/bsky/src/data-plane/server/routes/notifs.ts new file mode 100644 index 00000000000..d5289806128 --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/notifs.ts @@ -0,0 +1,115 @@ +import { sql } from 'kysely' +import { ServiceImpl } from '@connectrpc/connect' +import { Timestamp } from '@bufbuild/protobuf' +import { Service } from '../../../proto/bsky_connect' +import { Database } from '../db' +import { countAll, excluded, notSoftDeletedClause } from '../db/util' +import { TimeCidKeyset, paginate } from '../db/pagination' + +export default (db: Database): Partial> => ({ + async getNotifications(req) { + const { actorDid, limit, cursor } = req + const { ref } = db.db.dynamic + let builder = db.db + .selectFrom('notification as notif') + .where('notif.did', '=', actorDid) + .where((clause) => + clause + .where('reasonSubject', 'is', null) + .orWhereExists( + db.db + .selectFrom('record as subject') + .selectAll() + .whereRef('subject.uri', '=', ref('notif.reasonSubject')), + ), + ) + .select([ + 'notif.author as authorDid', + 'notif.recordUri as uri', + 'notif.recordCid as cid', + 'notif.reason as reason', + 'notif.reasonSubject as reasonSubject', + 'notif.sortAt as sortAt', + ]) + + const keyset = new TimeCidKeyset( + ref('notif.sortAt'), + ref('notif.recordCid'), + ) + builder = paginate(builder, { + cursor, + limit, + keyset, + tryIndex: true, + }) + + const notifsRes = await builder.execute() + const notifications = notifsRes.map((notif) => ({ + recipientDid: actorDid, + uri: notif.uri, + reason: notif.reason, + reasonSubject: notif.reasonSubject ?? undefined, + timestamp: Timestamp.fromDate(new Date(notif.sortAt)), + })) + return { + notifications, + cursor: keyset.packFromResult(notifsRes), + } + }, + + async getNotificationSeen(req) { + const res = await db.db + .selectFrom('actor_state') + .where('did', '=', req.actorDid) + .selectAll() + .executeTakeFirst() + if (!res) { + return {} + } + return { + timestamp: Timestamp.fromDate(new Date(res.lastSeenNotifs)), + } + }, + + async getUnreadNotificationCount(req) { + const { actorDid } = req + const { ref } = db.db.dynamic + const result = await db.db + .selectFrom('notification') + .select(countAll.as('count')) + .innerJoin('actor', 'actor.did', 'notification.did') + .leftJoin('actor_state', 'actor_state.did', 'actor.did') + .innerJoin('record', 'record.uri', 'notification.recordUri') + .where(notSoftDeletedClause(ref('record'))) + .where(notSoftDeletedClause(ref('actor'))) + // Ensure to hit notification_did_sortat_idx, handling case where lastSeenNotifs is null. + .where('notification.did', '=', actorDid) + .where( + 'notification.sortAt', + '>', + sql`coalesce(${ref('actor_state.lastSeenNotifs')}, ${''})`, + ) + .executeTakeFirst() + + return { + count: result?.count, + } + }, + + async updateNotificationSeen(req) { + const { actorDid, timestamp } = req + if (!timestamp) { + return + } + const lastSeenNotifs = timestamp.toDate().toISOString() + await db.db + .insertInto('actor_state') + .values({ did: actorDid, lastSeenNotifs }) + .onConflict((oc) => + oc.column('did').doUpdateSet({ + lastSeenNotifs: excluded(db.db, 'lastSeenNotifs'), + }), + ) + .executeTakeFirst() + }, +}) diff --git a/packages/bsky/src/data-plane/server/routes/posts.ts b/packages/bsky/src/data-plane/server/routes/posts.ts new file mode 100644 index 00000000000..8f90a34b3cc --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/posts.ts @@ -0,0 +1,21 @@ +import { ServiceImpl } from '@connectrpc/connect' +import { Service } from '../../../proto/bsky_connect' +import { keyBy } from '@atproto/common' +import { Database } from '../db' + +export default (db: Database): Partial> => ({ + async getPostReplyCounts(req) { + const uris = req.refs.map((ref) => ref.uri) + if (uris.length === 0) { + return { counts: [] } + } + const res = await db.db + .selectFrom('post_agg') + .select(['uri', 'replyCount']) + .where('uri', 'in', uris) + .execute() + const byUri = keyBy(res, 'uri') + const counts = uris.map((uri) => byUri[uri]?.replyCount ?? 0) + return { counts } + }, +}) diff --git a/packages/bsky/src/data-plane/server/routes/profile.ts b/packages/bsky/src/data-plane/server/routes/profile.ts new file mode 100644 index 00000000000..20768ed89aa --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/profile.ts @@ -0,0 +1,49 @@ +import { ServiceImpl } from '@connectrpc/connect' +import { Service } from '../../../proto/bsky_connect' +import { keyBy } from '@atproto/common' +import { getRecords } from './records' +import { Database } from '../db' + +export default (db: Database): Partial> => ({ + async getActors(req) { + const { dids } = req + if (dids.length === 0) { + return { actors: [] } + } + const profileUris = dids.map( + (did) => `at://${did}/app.bsky.actor.profile/self`, + ) + const [handlesRes, profiles] = await Promise.all([ + db.db.selectFrom('actor').where('did', 'in', dids).selectAll().execute(), + getRecords(db)({ uris: profileUris }), + ]) + const byDid = keyBy(handlesRes, 'did') + const actors = dids.map((did, i) => { + const row = byDid[did] + return { + exists: !!row, + handle: row?.handle ?? undefined, + profile: profiles.records[i], + takenDown: !!row?.takedownRef, + takedownRef: row?.takedownRef || undefined, + tombstonedAt: undefined, // in current implementation, tombstoned actors are deleted + } + }) + return { actors } + }, + + async getDidsByHandles(req) { + const { handles } = req + if (handles.length === 0) { + return { dids: [] } + } + const res = await db.db + .selectFrom('actor') + .where('handle', 'in', handles) + .selectAll() + .execute() + const byHandle = keyBy(res, 'handle') + const dids = handles.map((handle) => byHandle[handle]?.did ?? '') + return { dids } + }, +}) diff --git a/packages/bsky/src/data-plane/server/routes/records.ts b/packages/bsky/src/data-plane/server/routes/records.ts new file mode 100644 index 00000000000..47670a24412 --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/records.ts @@ -0,0 +1,95 @@ +import { keyBy } from '@atproto/common' +import { AtUri } from '@atproto/syntax' +import { Timestamp } from '@bufbuild/protobuf' +import { ServiceImpl } from '@connectrpc/connect' +import * as ui8 from 'uint8arrays' +import { ids } from '../../../lexicon/lexicons' +import { Service } from '../../../proto/bsky_connect' +import { PostRecordMeta, Record } from '../../../proto/bsky_pb' +import { Database } from '../db' + +export default (db: Database): Partial> => ({ + getBlockRecords: getRecords(db, ids.AppBskyGraphBlock), + getFeedGeneratorRecords: getRecords(db, ids.AppBskyFeedGenerator), + getFollowRecords: getRecords(db, ids.AppBskyGraphFollow), + getLikeRecords: getRecords(db, ids.AppBskyFeedLike), + getListBlockRecords: getRecords(db, ids.AppBskyGraphListblock), + getListItemRecords: getRecords(db, ids.AppBskyGraphListitem), + getListRecords: getRecords(db, ids.AppBskyGraphList), + getPostRecords: getPostRecords(db), + getProfileRecords: getRecords(db, ids.AppBskyActorProfile), + getRepostRecords: getRecords(db, ids.AppBskyFeedRepost), + getThreadGateRecords: getRecords(db, ids.AppBskyFeedThreadgate), +}) + +export const getRecords = + (db: Database, collection?: string) => + async (req: { uris: string[] }): Promise<{ records: Record[] }> => { + const validUris = collection + ? req.uris.filter((uri) => new AtUri(uri).collection === collection) + : req.uris + const res = validUris.length + ? await db.db + .selectFrom('record') + .selectAll() + .where('uri', 'in', validUris) + .execute() + : [] + const byUri = keyBy(res, 'uri') + const records: Record[] = req.uris.map((uri) => { + const row = byUri[uri] + const json = row ? row.json : JSON.stringify(null) + const createdAtRaw = new Date(JSON.parse(json)?.['createdAt']) + const createdAt = !isNaN(createdAtRaw.getTime()) + ? Timestamp.fromDate(createdAtRaw) + : undefined + const indexedAt = row?.indexedAt + ? Timestamp.fromDate(new Date(row?.indexedAt)) + : undefined + const recordBytes = ui8.fromString(json, 'utf8') + return new Record({ + record: recordBytes, + cid: row?.cid, + createdAt, + indexedAt, + sortedAt: compositeTime(createdAt, indexedAt), + takenDown: !!row?.takedownRef, + takedownRef: row?.takedownRef ?? undefined, + }) + }) + return { records } + } + +export const getPostRecords = (db: Database) => { + const getBaseRecords = getRecords(db, ids.AppBskyFeedPost) + return async (req: { + uris: string[] + }): Promise<{ records: Record[]; meta: PostRecordMeta[] }> => { + const [{ records }, details] = await Promise.all([ + getBaseRecords(req), + req.uris.length + ? await db.db + .selectFrom('post') + .where('uri', 'in', req.uris) + .select(['uri', 'violatesThreadGate']) + .execute() + : [], + ]) + const byKey = keyBy(details, 'uri') + const meta = req.uris.map((uri) => { + return new PostRecordMeta({ + violatesThreadGate: !!byKey[uri]?.violatesThreadGate, + }) + }) + return { records, meta } + } +} + +const compositeTime = ( + ts1: Timestamp | undefined, + ts2: Timestamp | undefined, +) => { + if (!ts1) return ts2 + if (!ts2) return ts1 + return ts1.toDate() < ts2.toDate() ? ts1 : ts2 +} diff --git a/packages/bsky/src/data-plane/server/routes/relationships.ts b/packages/bsky/src/data-plane/server/routes/relationships.ts new file mode 100644 index 00000000000..d6029e169e8 --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/relationships.ts @@ -0,0 +1,148 @@ +import { sql } from 'kysely' +import { ServiceImpl } from '@connectrpc/connect' +import { keyBy } from '@atproto/common' +import { Service } from '../../../proto/bsky_connect' +import { Database } from '../db' +import { valuesList } from '../db/util' + +export default (db: Database): Partial> => ({ + async getRelationships(req) { + const { actorDid, targetDids } = req + if (targetDids.length === 0) { + return { relationships: [] } + } + const { ref } = db.db.dynamic + const res = await db.db + .selectFrom('actor') + .where('did', 'in', targetDids) + .select([ + 'actor.did', + db.db + .selectFrom('mute') + .where('mute.mutedByDid', '=', actorDid) + .whereRef('mute.subjectDid', '=', ref('actor.did')) + .select(sql`${true}`.as('val')) + .as('muted'), + db.db + .selectFrom('list_item') + .innerJoin('list_mute', 'list_mute.listUri', 'list_item.listUri') + .where('list_mute.mutedByDid', '=', actorDid) + .whereRef('list_item.subjectDid', '=', ref('actor.did')) + .select('list_item.listUri') + .as('mutedByList'), + db.db + .selectFrom('actor_block') + .where('actor_block.creator', '=', actorDid) + .whereRef('actor_block.subjectDid', '=', ref('actor.did')) + .select('uri') + .as('blocking'), + db.db + .selectFrom('actor_block') + .where('actor_block.subjectDid', '=', actorDid) + .whereRef('actor_block.creator', '=', ref('actor.did')) + .select('uri') + .as('blockedBy'), + db.db + .selectFrom('list_item') + .innerJoin('list_block', 'list_block.subjectUri', 'list_item.listUri') + .where('list_block.creator', '=', actorDid) + .whereRef('list_item.subjectDid', '=', ref('actor.did')) + .select('list_item.listUri') + .as('blockingByList'), + db.db + .selectFrom('list_item') + .innerJoin('list_block', 'list_block.subjectUri', 'list_item.listUri') + .where('list_item.subjectDid', '=', actorDid) + .whereRef('list_block.creator', '=', ref('actor.did')) + .select('list_item.listUri') + .as('blockedByList'), + db.db + .selectFrom('follow') + .where('follow.creator', '=', actorDid) + .whereRef('follow.subjectDid', '=', ref('actor.did')) + .select('uri') + .as('following'), + db.db + .selectFrom('follow') + .where('follow.subjectDid', '=', actorDid) + .whereRef('follow.creator', '=', ref('actor.did')) + .select('uri') + .as('followedBy'), + ]) + .execute() + const byDid = keyBy(res, 'did') + const relationships = targetDids.map((did) => { + const row = byDid[did] ?? {} + return { + muted: row.muted ?? false, + mutedByList: row.mutedByList ?? '', + blockedBy: row.blockedBy ?? '', + blocking: row.blocking ?? '', + blockedByList: row.blockedByList ?? '', + blockingByList: row.blockingByList ?? '', + following: row.following ?? '', + followedBy: row.followedBy ?? '', + } + }) + return { relationships } + }, + + async getBlockExistence(req) { + const { pairs } = req + if (pairs.length === 0) { + return { exists: [] } + } + const { ref } = db.db.dynamic + const sourceRef = ref('pair.source') + const targetRef = ref('pair.target') + const values = valuesList(pairs.map((p) => sql`${p.a}, ${p.b}`)) + const res = await db.db + .selectFrom(values.as(sql`pair (source, target)`)) + .select([ + sql`${sourceRef}`.as('source'), + sql`${targetRef}`.as('target'), + ]) + .whereExists((qb) => + qb + .selectFrom('actor_block') + .whereRef('actor_block.creator', '=', sourceRef) + .whereRef('actor_block.subjectDid', '=', targetRef) + .select('uri'), + ) + .orWhereExists((qb) => + qb + .selectFrom('actor_block') + .whereRef('actor_block.creator', '=', targetRef) + .whereRef('actor_block.subjectDid', '=', sourceRef) + .select('uri'), + ) + .orWhereExists((qb) => + qb + .selectFrom('list_item') + .innerJoin('list_block', 'list_block.subjectUri', 'list_item.listUri') + .whereRef('list_block.creator', '=', sourceRef) + .whereRef('list_item.subjectDid', '=', targetRef) + .select('list_item.listUri'), + ) + .orWhereExists((qb) => + qb + .selectFrom('list_item') + .innerJoin('list_block', 'list_block.subjectUri', 'list_item.listUri') + .whereRef('list_block.creator', '=', targetRef) + .whereRef('list_item.subjectDid', '=', sourceRef) + .select('list_item.listUri'), + ) + .execute() + const existMap = res.reduce((acc, cur) => { + const key = [cur.source, cur.target].sort().join(',') + return acc.set(key, true) + }, new Map()) + const exists = pairs.map((pair) => { + const key = [pair.a, pair.b].sort().join(',') + return existMap.get(key) === true + }) + return { + exists, + } + }, +}) diff --git a/packages/bsky/src/data-plane/server/routes/reposts.ts b/packages/bsky/src/data-plane/server/routes/reposts.ts new file mode 100644 index 00000000000..9c6f72435c0 --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/reposts.ts @@ -0,0 +1,77 @@ +import { keyBy } from '@atproto/common' +import { ServiceImpl } from '@connectrpc/connect' +import { Service } from '../../../proto/bsky_connect' +import { Database } from '../db' +import { TimeCidKeyset, paginate } from '../db/pagination' + +export default (db: Database): Partial> => ({ + async getRepostsBySubject(req) { + const { subject, cursor, limit } = req + const { ref } = db.db.dynamic + + let builder = db.db + .selectFrom('repost') + .where('repost.subject', '=', subject?.uri ?? '') + .selectAll('repost') + + const keyset = new TimeCidKeyset(ref('repost.sortAt'), ref('repost.cid')) + builder = paginate(builder, { + limit, + cursor, + keyset, + }) + + const reposts = await builder.execute() + + return { + uris: reposts.map((l) => l.uri), + cursor: keyset.packFromResult(reposts), + } + }, + + async getRepostsByActorAndSubjects(req) { + const { actorDid, refs } = req + if (refs.length === 0) { + return { uris: [] } + } + const res = await db.db + .selectFrom('repost') + .where('creator', '=', actorDid) + .where( + 'subject', + 'in', + refs.map(({ uri }) => uri), + ) + .selectAll() + .execute() + const bySubject = keyBy(res, 'subject') + // @TODO handling undefineds properly, or do we need to turn them into empty strings? + const uris = refs.map(({ uri }) => bySubject[uri]?.uri) + return { uris } + }, + + async getActorReposts(req) { + const { actorDid, limit, cursor } = req + const { ref } = db.db.dynamic + + let builder = db.db + .selectFrom('repost') + .where('repost.creator', '=', actorDid) + .selectAll() + + const keyset = new TimeCidKeyset(ref('repost.sortAt'), ref('repost.cid')) + + builder = paginate(builder, { + limit, + cursor, + keyset, + }) + + const reposts = await builder.execute() + + return { + uris: reposts.map((l) => l.uri), + cursor: keyset.packFromResult(reposts), + } + }, +}) diff --git a/packages/bsky/src/data-plane/server/routes/search.ts b/packages/bsky/src/data-plane/server/routes/search.ts new file mode 100644 index 00000000000..7b3b12d8c29 --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/search.ts @@ -0,0 +1,61 @@ +import { ServiceImpl } from '@connectrpc/connect' +import { Service } from '../../../proto/bsky_connect' +import { Database } from '../db' +import { IndexedAtDidKeyset, TimeCidKeyset, paginate } from '../db/pagination' + +export default (db: Database): Partial> => ({ + // @TODO actor search endpoints still fall back to search service + async searchActors(req) { + const { term, limit, cursor } = req + const { ref } = db.db.dynamic + let builder = db.db + .selectFrom('actor') + .where('actor.handle', 'like', `%${cleanQuery(term)}%`) + .selectAll() + + const keyset = new IndexedAtDidKeyset( + ref('actor.indexedAt'), + ref('actor.did'), + ) + builder = paginate(builder, { + limit, + cursor, + keyset, + tryIndex: true, + }) + + const res = await builder.execute() + + return { + dids: res.map((row) => row.did), + cursor: keyset.packFromResult(res), + } + }, + + // @TODO post search endpoint still falls back to search service + async searchPosts(req) { + const { term, limit, cursor } = req + const { ref } = db.db.dynamic + let builder = db.db + .selectFrom('post') + .where('post.text', 'like', `%${term}%`) + .selectAll() + + const keyset = new TimeCidKeyset(ref('post.sortAt'), ref('post.cid')) + builder = paginate(builder, { + limit, + cursor, + keyset, + tryIndex: true, + }) + + const res = await builder.execute() + return { + uris: res.map((row) => row.uri), + cursor: keyset.packFromResult(res), + } + }, +}) + +// Remove leading @ in case a handle is input that way +const cleanQuery = (query: string) => query.trim().replace(/^@/g, '') diff --git a/packages/bsky/src/data-plane/server/routes/suggestions.ts b/packages/bsky/src/data-plane/server/routes/suggestions.ts new file mode 100644 index 00000000000..68b1dc54d5b --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/suggestions.ts @@ -0,0 +1,175 @@ +import { sql } from 'kysely' +import { ServiceImpl } from '@connectrpc/connect' +import { Service } from '../../../proto/bsky_connect' +import { Database } from '../db' + +export default (db: Database): Partial> => ({ + async getFollowSuggestions(req) { + const { actorDid, relativeToDid, cursor, limit } = req + if (relativeToDid) { + return getFollowSuggestionsRelativeTo(db, { + actorDid, + relativeToDid, + cursor: cursor || undefined, + limit: limit || undefined, + }) + } else { + return getFollowSuggestionsGlobal(db, { + actorDid, + cursor: cursor || undefined, + limit: limit || undefined, + }) + } + }, + + async getSuggestedEntities() { + const entities = await db.db + .selectFrom('tagged_suggestion') + .selectAll() + .execute() + return { + entities, + } + }, +}) + +const getFollowSuggestionsGlobal = async ( + db: Database, + input: { actorDid: string; cursor?: string; limit?: number }, +) => { + const alreadyIncluded = parseCursor(input.cursor) + const suggestions = await db.db + .selectFrom('suggested_follow') + .innerJoin('actor', 'actor.did', 'suggested_follow.did') + .if(alreadyIncluded.length > 0, (qb) => + qb.where('suggested_follow.order', 'not in', alreadyIncluded), + ) + .selectAll() + .orderBy('suggested_follow.order', 'asc') + .execute() + + // always include first two + const firstTwo = suggestions.filter( + (row) => row.order === 1 || row.order === 2, + ) + const rest = suggestions.filter((row) => row.order !== 1 && row.order !== 2) + const limited = firstTwo.concat(shuffle(rest)).slice(0, input.limit) + + // if the result set ends up getting larger, consider using a seed included in the cursor for for the randomized shuffle + const cursor = + limited.length > 0 + ? limited + .map((row) => row.order.toString()) + .concat(alreadyIncluded.map((id) => id.toString())) + .join(':') + : undefined + + return { + dids: limited.map((s) => s.did), + cursor, + } +} + +const getFollowSuggestionsRelativeTo = async ( + db: Database, + input: { + actorDid: string + relativeToDid: string + cursor?: string + limit?: number + }, +) => { + if (input.cursor) return { dids: [] } + const limit = input.limit ? Math.min(10, input.limit) : 10 + const actorsViewerFollows = db.db + .selectFrom('follow') + .where('creator', '=', input.actorDid) + .select('subjectDid') + const mostLikedAccounts = await db.db + .selectFrom( + db.db + .selectFrom('like') + .where('creator', '=', input.relativeToDid) + .select(sql`split_part(subject, '/', 3)`.as('subjectDid')) + .orderBy('sortAt', 'desc') + .limit(1000) // limit to 1000 + .as('likes'), + ) + .select('likes.subjectDid as did') + .select((qb) => qb.fn.count('likes.subjectDid').as('count')) + .where('likes.subjectDid', 'not in', actorsViewerFollows) + .where('likes.subjectDid', 'not in', [input.actorDid, input.relativeToDid]) + .groupBy('likes.subjectDid') + .orderBy('count', 'desc') + .limit(limit) + .execute() + const resultDids = mostLikedAccounts.map((a) => ({ did: a.did })) as { + did: string + }[] + + if (resultDids.length < limit) { + // backfill with popular accounts followed by actor + const mostPopularAccountsActorFollows = await db.db + .selectFrom('follow') + .innerJoin('profile_agg', 'follow.subjectDid', 'profile_agg.did') + .select('follow.subjectDid as did') + .where('follow.creator', '=', input.actorDid) + .where('follow.subjectDid', '!=', input.relativeToDid) + .where('follow.subjectDid', 'not in', actorsViewerFollows) + .if(resultDids.length > 0, (qb) => + qb.where( + 'subjectDid', + 'not in', + resultDids.map((a) => a.did), + ), + ) + .orderBy('profile_agg.followersCount', 'desc') + .limit(limit) + .execute() + + resultDids.push(...mostPopularAccountsActorFollows) + } + + if (resultDids.length < limit) { + // backfill with suggested_follow table + const additional = await db.db + .selectFrom('suggested_follow') + .where( + 'did', + 'not in', + // exclude any we already have + resultDids + .map((a) => a.did) + .concat([input.actorDid, input.relativeToDid]), + ) + // and aren't already followed by viewer + .where('did', 'not in', actorsViewerFollows) + .selectAll() + .execute() + + resultDids.push(...additional) + } + + return { dids: resultDids.map((x) => x.did) } +} + +const parseCursor = (cursor?: string): number[] => { + if (!cursor) { + return [] + } + try { + return cursor + .split(':') + .map((id) => parseInt(id, 10)) + .filter((id) => !isNaN(id)) + } catch { + return [] + } +} + +const shuffle = (arr: T[]): T[] => { + return arr + .map((value) => ({ value, sort: Math.random() })) + .sort((a, b) => a.sort - b.sort) + .map(({ value }) => value) +} diff --git a/packages/bsky/src/data-plane/server/routes/sync.ts b/packages/bsky/src/data-plane/server/routes/sync.ts new file mode 100644 index 00000000000..90222b628e7 --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/sync.ts @@ -0,0 +1,16 @@ +import { ServiceImpl } from '@connectrpc/connect' +import { Service } from '../../../proto/bsky_connect' +import { Database } from '../db' + +export default (db: Database): Partial> => ({ + async getLatestRev(req) { + const res = await db.db + .selectFrom('actor_sync') + .where('did', '=', req.actorDid) + .select('repoRev') + .executeTakeFirst() + return { + rev: res?.repoRev ?? undefined, + } + }, +}) diff --git a/packages/bsky/src/data-plane/server/routes/threads.ts b/packages/bsky/src/data-plane/server/routes/threads.ts new file mode 100644 index 00000000000..5e19451d465 --- /dev/null +++ b/packages/bsky/src/data-plane/server/routes/threads.ts @@ -0,0 +1,33 @@ +import { ServiceImpl } from '@connectrpc/connect' +import { Service } from '../../../proto/bsky_connect' +import { Database } from '../db' +import { getAncestorsAndSelfQb, getDescendentsQb } from '../util' + +export default (db: Database): Partial> => ({ + async getThread(req) { + const { postUri, above, below } = req + const [ancestors, descendents] = await Promise.all([ + getAncestorsAndSelfQb(db.db, { + uri: postUri, + parentHeight: above, + }) + .selectFrom('ancestor') + .selectAll() + .execute(), + getDescendentsQb(db.db, { + uri: postUri, + depth: below, + }) + .selectFrom('descendent') + .innerJoin('post', 'post.uri', 'descendent.uri') + .orderBy('post.sortAt', 'desc') + .selectAll() + .execute(), + ]) + const uris = [ + ...ancestors.map((p) => p.uri), + ...descendents.map((p) => p.uri), + ] + return { uris } + }, +}) diff --git a/packages/bsky/src/indexer/subscription.ts b/packages/bsky/src/data-plane/server/subscription/index.ts similarity index 57% rename from packages/bsky/src/indexer/subscription.ts rename to packages/bsky/src/data-plane/server/subscription/index.ts index 907da954f49..a020eb0542d 100644 --- a/packages/bsky/src/indexer/subscription.ts +++ b/packages/bsky/src/data-plane/server/subscription/index.ts @@ -1,8 +1,10 @@ import assert from 'node:assert' import { CID } from 'multiformats/cid' import { AtUri } from '@atproto/syntax' -import { cborDecode, wait, handleAllSettledErrors } from '@atproto/common' -import { DisconnectError } from '@atproto/xrpc-server' +import { Subscription } from '@atproto/xrpc-server' +import { cborDecode, handleAllSettledErrors } from '@atproto/common' +import { ValidationError } from '@atproto/lexicon' +import { IdResolver } from '@atproto/identity' import { WriteOpAction, readCarWithRoot, @@ -10,136 +12,89 @@ import { def, Commit, } from '@atproto/repo' -import { ValidationError } from '@atproto/lexicon' -import * as message from '../lexicon/types/com/atproto/sync/subscribeRepos' -import { Leader } from '../db/leader' -import { IndexingService } from '../services/indexing' -import log from './logger' +import { ids, lexicons } from '../../../lexicon/lexicons' +import { OutputSchema as Message } from '../../../lexicon/types/com/atproto/sync/subscribeRepos' +import * as message from '../../../lexicon/types/com/atproto/sync/subscribeRepos' +import { subLogger as log } from '../../../logger' +import { IndexingService } from '../indexing' +import { Database } from '../db' import { ConsecutiveItem, ConsecutiveList, - LatestQueue, PartitionedQueue, - PerfectMap, ProcessableMessage, - jitter, loggableMessage, - strToInt, -} from '../subscription/util' -import IndexerContext from './context' +} from './util' +import { BackgroundQueue } from '../background' -export const INDEXER_SUB_LOCK_ID = 1200 // need one per partition - -export class IndexerSubscription { - destroyed = false - leader = new Leader(this.opts.subLockId || INDEXER_SUB_LOCK_ID, this.ctx.db) - processedCount = 0 - repoQueue = new PartitionedQueue({ - concurrency: this.opts.concurrency ?? Infinity, - }) - partitions = new PerfectMap() - partitionIds = this.opts.partitionIds +export class RepoSubscription { + ac = new AbortController() + running: Promise | undefined + cursor = 0 + seenSeq: number | null = null + repoQueue = new PartitionedQueue({ concurrency: Infinity }) + consecutive = new ConsecutiveList() + background: BackgroundQueue indexingSvc: IndexingService constructor( - public ctx: IndexerContext, - public opts: { - partitionIds: number[] - subLockId?: number - concurrency?: number - partitionBatchSize?: number + private opts: { + service: string + db: Database + idResolver: IdResolver + background: BackgroundQueue }, ) { - this.indexingSvc = ctx.services.indexing(ctx.db) + this.background = new BackgroundQueue(this.opts.db) + this.indexingSvc = new IndexingService( + this.opts.db, + this.opts.idResolver, + this.background, + ) } - async processEvents(opts: { signal: AbortSignal }) { - const done = () => this.destroyed || opts.signal.aborted - while (!done()) { - const results = await this.ctx.redis.readStreams( - this.partitionIds.map((id) => ({ - key: partitionKey(id), - cursor: this.partitions.get(id).cursor, - })), - { - blockMs: 1000, - count: this.opts.partitionBatchSize ?? 50, // events per stream - }, - ) - if (done()) break - for (const { key, messages } of results) { - const partition = this.partitions.get(partitionId(key)) - for (const msg of messages) { - const seq = strToInt(msg.cursor) - const envelope = getEnvelope(msg.contents) - partition.cursor = seq - const item = partition.consecutive.push(seq) - this.repoQueue.add(envelope.repo, async () => { - await this.handleMessage(partition, item, envelope) - }) + run() { + if (this.running) return + this.ac = new AbortController() + this.repoQueue = new PartitionedQueue({ concurrency: Infinity }) + this.consecutive = new ConsecutiveList() + this.running = this.process() + .catch((err) => { + if (err.name !== 'AbortError') { + // allow this to cause an unhandled rejection, let deployment handle the crash. + log.error({ err }, 'subscription crashed') + throw err } - } - await this.repoQueue.main.onEmpty() // backpressure - } + }) + .finally(() => (this.running = undefined)) } - async run() { - while (!this.destroyed) { - try { - const { ran } = await this.leader.run(async ({ signal }) => { - // initialize cursors to 0 (read from beginning of stream) - for (const id of this.partitionIds) { - this.partitions.set(id, new Partition(id, 0)) - } - // process events - await this.processEvents({ signal }) - }) - if (ran && !this.destroyed) { - throw new Error('Indexer sub completed, but should be persistent') - } - } catch (err) { - log.error({ err }, 'indexer sub error') - } - if (!this.destroyed) { - await wait(5000 + jitter(1000)) // wait then try to become leader + private async process() { + const sub = this.getSubscription() + for await (const msg of sub) { + const details = getMessageDetails(msg) + if ('info' in details) { + // These messages are not sequenced, we just log them and carry on + log.warn( + { provider: this.opts.service, message: loggableMessage(msg) }, + `sub ${details.info ? 'info' : 'unknown'} message`, + ) + continue } + const item = this.consecutive.push(details.seq) + this.repoQueue.add(details.repo, async () => { + await this.handleMessage(item, details) + }) + this.seenSeq = details.seq + await this.repoQueue.main.onEmpty() // backpressure } } - async requestReprocess(did: string) { - await this.repoQueue.add(did, async () => { - try { - await this.indexingSvc.indexRepo(did, undefined) - } catch (err) { - log.error({ did }, 'failed to reprocess repo') - } - }) - } - - async destroy() { - this.destroyed = true - await this.repoQueue.destroy() - await Promise.all( - [...this.partitions.values()].map((p) => p.cursorQueue.destroy()), - ) - this.leader.destroy(new DisconnectError()) - } - - async resume() { - this.destroyed = false - this.partitions = new Map() - this.repoQueue = new PartitionedQueue({ - concurrency: this.opts.concurrency ?? Infinity, - }) - await this.run() - } - private async handleMessage( - partition: Partition, item: ConsecutiveItem, envelope: Envelope, ) { - const msg = envelope.event + const msg = envelope.message try { if (message.isCommit(msg)) { await this.handleCommit(msg) @@ -163,16 +118,9 @@ export class IndexerSubscription { 'indexer message processing error', ) } finally { - this.processedCount++ const latest = item.complete().at(-1) if (latest !== undefined) { - partition.cursorQueue - .add(async () => { - await this.ctx.redis.trimStream(partition.key, latest + 1) - }) - .catch((err) => { - log.error({ err }, 'indexer cursor error') - }) + this.cursor = latest } } } @@ -253,6 +201,83 @@ export class IndexerSubscription { private async handleTombstone(msg: message.Tombstone) { await this.indexingSvc.tombstoneActor(msg.did) } + + private getSubscription() { + return new Subscription({ + service: this.opts.service, + method: ids.ComAtprotoSyncSubscribeRepos, + signal: this.ac.signal, + getParams: async () => { + return { cursor: this.cursor } + }, + onReconnectError: (err, reconnects, initial) => { + log.warn({ err, reconnects, initial }, 'sub reconnect') + }, + validate: (value) => { + try { + return lexicons.assertValidXrpcMessage( + ids.ComAtprotoSyncSubscribeRepos, + value, + ) + } catch (err) { + log.warn( + { + err, + seq: ifNumber(value?.['seq']), + repo: ifString(value?.['repo']), + commit: ifString(value?.['commit']?.toString()), + time: ifString(value?.['time']), + provider: this.opts.service, + }, + 'ingester sub skipped invalid message', + ) + } + }, + }) + } + + async destroy() { + this.ac.abort() + await this.running + await this.repoQueue.destroy() + await this.background.processAll() + } +} + +type Envelope = { + repo: string + message: ProcessableMessage +} + +function ifString(val: unknown): string | undefined { + return typeof val === 'string' ? val : undefined +} + +function ifNumber(val: unknown): number | undefined { + return typeof val === 'number' ? val : undefined +} + +function getMessageDetails(msg: Message): + | { info: message.Info | null } + | { + seq: number + repo: string + message: ProcessableMessage + } { + if (message.isCommit(msg)) { + return { seq: msg.seq, repo: msg.repo, message: msg } + } else if (message.isHandle(msg)) { + return { seq: msg.seq, repo: msg.did, message: msg } + } else if (message.isIdentity(msg)) { + return { seq: msg.seq, repo: msg.did, message: msg } + } else if (message.isMigrate(msg)) { + return { seq: msg.seq, repo: msg.did, message: msg } + } else if (message.isTombstone(msg)) { + return { seq: msg.seq, repo: msg.did, message: msg } + } else if (message.isInfo(msg)) { + return { info: msg } + } + return { info: null } } async function getOps( @@ -296,37 +321,6 @@ async function getOps( return { root, rootCid: car.root, ops } } -function getEnvelope(val: Record): Envelope { - assert(val.repo && val.event, 'malformed message contents') - return { - repo: val.repo.toString(), - event: cborDecode(val.event) as ProcessableMessage, - } -} - -type Envelope = { - repo: string - event: ProcessableMessage -} - -class Partition { - consecutive = new ConsecutiveList() - cursorQueue = new LatestQueue() - constructor(public id: number, public cursor: number) {} - get key() { - return partitionKey(this.id) - } -} - -function partitionId(key: string) { - assert(key.startsWith('repo:')) - return strToInt(key.replace('repo:', '')) -} - -function partitionKey(p: number) { - return `repo:${p}` -} - type PreparedCreate = { action: WriteOpAction.Create uri: AtUri diff --git a/packages/bsky/src/subscription/util.ts b/packages/bsky/src/data-plane/server/subscription/util.ts similarity index 93% rename from packages/bsky/src/subscription/util.ts rename to packages/bsky/src/data-plane/server/subscription/util.ts index fe367bcc24c..40bf30f73c8 100644 --- a/packages/bsky/src/subscription/util.ts +++ b/packages/bsky/src/data-plane/server/subscription/util.ts @@ -1,7 +1,7 @@ -import PQueue from 'p-queue' -import { OutputSchema as RepoMessage } from '../lexicon/types/com/atproto/sync/subscribeRepos' -import * as message from '../lexicon/types/com/atproto/sync/subscribeRepos' import assert from 'node:assert' +import PQueue from 'p-queue' +import { OutputSchema as RepoMessage } from '../../../lexicon/types/com/atproto/sync/subscribeRepos' +import * as message from '../../../lexicon/types/com/atproto/sync/subscribeRepos' // A queue with arbitrarily many partitions, each processing work sequentially. // Partitions are created lazily and taken out of memory when they go idle. @@ -108,6 +108,7 @@ export class PerfectMap extends Map { export type ProcessableMessage = | message.Commit | message.Handle + | message.Identity | message.Migrate | message.Tombstone @@ -127,6 +128,8 @@ export function loggableMessage(msg: RepoMessage) { } } else if (message.isHandle(msg)) { return msg + } else if (message.isIdentity(msg)) { + return msg } else if (message.isMigrate(msg)) { return msg } else if (message.isTombstone(msg)) { diff --git a/packages/bsky/src/services/feed/util.ts b/packages/bsky/src/data-plane/server/util.ts similarity index 55% rename from packages/bsky/src/services/feed/util.ts rename to packages/bsky/src/data-plane/server/util.ts index 83b5e59d705..d15b7ffa518 100644 --- a/packages/bsky/src/services/feed/util.ts +++ b/packages/bsky/src/data-plane/server/util.ts @@ -1,19 +1,77 @@ import { sql } from 'kysely' import { AtUri } from '@atproto/syntax' +import { ids } from '../../lexicon/lexicons' import { Record as PostRecord, ReplyRef, } from '../../lexicon/types/app/bsky/feed/post' -import { - Record as GateRecord, - isFollowingRule, - isListRule, - isMentionRule, -} from '../../lexicon/types/app/bsky/feed/threadgate' -import { isMention } from '../../lexicon/types/app/bsky/richtext/facet' -import { valuesList } from '../../db/util' -import DatabaseSchema from '../../db/database-schema' -import { ids } from '../../lexicon/lexicons' +import { Record as GateRecord } from '../../lexicon/types/app/bsky/feed/threadgate' +import DatabaseSchema from './db/database-schema' +import { valuesList } from './db/util' +import { parseThreadGate } from '../../views/util' + +export const getDescendentsQb = ( + db: DatabaseSchema, + opts: { + uri: string + depth: number // required, protects against cycles + }, +) => { + const { uri, depth } = opts + const query = db.withRecursive('descendent(uri, depth)', (cte) => { + return cte + .selectFrom('post') + .select(['post.uri as uri', sql`1`.as('depth')]) + .where(sql`1`, '<=', depth) + .where('replyParent', '=', uri) + .unionAll( + cte + .selectFrom('post') + .innerJoin('descendent', 'descendent.uri', 'post.replyParent') + .where('descendent.depth', '<', depth) + .select([ + 'post.uri as uri', + sql`descendent.depth + 1`.as('depth'), + ]), + ) + }) + return query +} + +export const getAncestorsAndSelfQb = ( + db: DatabaseSchema, + opts: { + uri: string + parentHeight: number // required, protects against cycles + }, +) => { + const { uri, parentHeight } = opts + const query = db.withRecursive( + 'ancestor(uri, ancestorUri, height)', + (cte) => { + return cte + .selectFrom('post') + .select([ + 'post.uri as uri', + 'post.replyParent as ancestorUri', + sql`0`.as('height'), + ]) + .where('uri', '=', uri) + .unionAll( + cte + .selectFrom('post') + .innerJoin('ancestor', 'ancestor.ancestorUri', 'post.uri') + .where('ancestor.height', '<', parentHeight) + .select([ + 'post.uri as uri', + 'post.replyParent as ancestorUri', + sql`ancestor.height + 1`.as('height'), + ]), + ) + }, + ) + return query +} export const invalidReplyRoot = ( reply: ReplyRef, @@ -35,46 +93,6 @@ export const invalidReplyRoot = ( // replying to a reply: ensure the parent is a reply for the same root post return parent.record.reply?.root.uri !== replyRoot } - -type ParsedThreadGate = { - canReply?: boolean - allowMentions?: boolean - allowFollowing?: boolean - allowListUris?: string[] -} - -export const parseThreadGate = ( - replierDid: string, - ownerDid: string, - rootPost: PostRecord | null, - gate: GateRecord | null, -): ParsedThreadGate => { - if (replierDid === ownerDid) { - return { canReply: true } - } - // if gate.allow is unset then *any* reply is allowed, if it is an empty array then *no* reply is allowed - if (!gate || !gate.allow) { - return { canReply: true } - } - - const allowMentions = !!gate.allow.find(isMentionRule) - const allowFollowing = !!gate.allow.find(isFollowingRule) - const allowListUris = gate.allow?.filter(isListRule).map((item) => item.list) - - // check mentions first since it's quick and synchronous - if (allowMentions) { - const isMentioned = rootPost?.facets?.some((facet) => { - return facet.features.some( - (item) => isMention(item) && item.did === replierDid, - ) - }) - if (isMentioned) { - return { canReply: true, allowMentions, allowFollowing, allowListUris } - } - } - return { allowMentions, allowFollowing, allowListUris } -} - export const violatesThreadGate = async ( db: DatabaseSchema, replierDid: string, @@ -132,9 +150,3 @@ export const postToThreadgateUri = (postUri: string) => { gateUri.collection = ids.AppBskyFeedThreadgate return gateUri.toString() } - -export const threadgateToPostUri = (gateUri: string) => { - const postUri = new AtUri(gateUri) - postUri.collection = ids.AppBskyFeedPost - return postUri.toString() -} diff --git a/packages/bsky/src/db/coordinator.ts b/packages/bsky/src/db/coordinator.ts deleted file mode 100644 index a8f4cc3016c..00000000000 --- a/packages/bsky/src/db/coordinator.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Migrator } from 'kysely' -import PrimaryDatabase from './primary' -import Database from './db' -import { PgOptions } from './types' -import { dbLogger } from '../logger' - -type ReplicaTag = 'timeline' | 'feed' | 'search' | 'thread' | '*' -type ReplicaOptions = PgOptions & { tags?: ReplicaTag[] } - -type CoordinatorOptions = { - schema?: string - primary: PgOptions - replicas?: ReplicaOptions[] -} - -type ReplicaGroup = { - dbs: Database[] - roundRobinIdx: number -} - -export class DatabaseCoordinator { - migrator: Migrator - destroyed = false - - private primary: PrimaryDatabase - private allReplicas: Database[] - private tagged: Record - private untagged: ReplicaGroup - private tagWarns = new Set() - - constructor(public opts: CoordinatorOptions) { - this.primary = new PrimaryDatabase({ - schema: opts.schema, - ...opts.primary, - }) - this.allReplicas = [] - this.tagged = {} - this.untagged = { - dbs: [], - roundRobinIdx: 0, - } - for (const cfg of opts.replicas ?? []) { - const db = new Database({ - schema: opts.schema, - ...cfg, - }) - this.allReplicas.push(db) - // setup different groups of replicas based on tag, each round-robins separately. - if (cfg.tags?.length) { - for (const tag of cfg.tags) { - if (tag === '*') { - this.untagged.dbs.push(db) - } else { - this.tagged[tag] ??= { - dbs: [], - roundRobinIdx: 0, - } - this.tagged[tag].dbs.push(db) - } - } - } else { - this.untagged.dbs.push(db) - } - } - // guarantee there is always a replica around to service any query, falling back to primary. - if (!this.untagged.dbs.length) { - if (this.allReplicas.length) { - this.untagged.dbs = [...this.allReplicas] - } else { - this.untagged.dbs = [this.primary] - } - } - } - - getPrimary(): PrimaryDatabase { - return this.primary - } - - getReplicas(): Database[] { - return this.allReplicas - } - - getReplica(tag?: ReplicaTag): Database { - if (tag && this.tagged[tag]) { - return nextDb(this.tagged[tag]) - } - if (tag && !this.tagWarns.has(tag)) { - this.tagWarns.add(tag) - dbLogger.warn({ tag }, 'no replica for tag, falling back to any replica') - } - return nextDb(this.untagged) - } - - async close(): Promise { - await Promise.all([ - this.primary.close(), - ...this.allReplicas.map((db) => db.close()), - ]) - } -} - -// @NOTE mutates group incrementing roundRobinIdx -const nextDb = (group: ReplicaGroup) => { - const db = group.dbs[group.roundRobinIdx] - group.roundRobinIdx = (group.roundRobinIdx + 1) % group.dbs.length - return db -} diff --git a/packages/bsky/src/db/db.ts b/packages/bsky/src/db/db.ts deleted file mode 100644 index cb58eb4742b..00000000000 --- a/packages/bsky/src/db/db.ts +++ /dev/null @@ -1,91 +0,0 @@ -import assert from 'assert' -import { Kysely, PostgresDialect } from 'kysely' -import { Pool as PgPool, types as pgTypes } from 'pg' -import DatabaseSchema, { DatabaseSchemaType } from './database-schema' -import { PgOptions } from './types' -import { dbLogger } from '../logger' - -export class Database { - pool: PgPool - db: DatabaseSchema - destroyed = false - isPrimary = false - - constructor( - public opts: PgOptions, - instances?: { db: DatabaseSchema; pool: PgPool }, - ) { - // if instances are provided, use those - if (instances) { - this.db = instances.db - this.pool = instances.pool - return - } - - // else create a pool & connect - const { schema, url } = opts - const pool = - opts.pool ?? - new PgPool({ - connectionString: url, - max: opts.poolSize, - maxUses: opts.poolMaxUses, - idleTimeoutMillis: opts.poolIdleTimeoutMs, - }) - - // Select count(*) and other pg bigints as js integer - pgTypes.setTypeParser(pgTypes.builtins.INT8, (n) => parseInt(n, 10)) - - // Setup schema usage, primarily for test parallelism (each test suite runs in its own pg schema) - if (schema && !/^[a-z_]+$/i.test(schema)) { - throw new Error(`Postgres schema must only contain [A-Za-z_]: ${schema}`) - } - - pool.on('error', onPoolError) - pool.on('connect', (client) => { - client.on('error', onClientError) - // Used for trigram indexes, e.g. on actor search - client.query('SET pg_trgm.word_similarity_threshold TO .4;') - if (schema) { - // Shared objects such as extensions will go in the public schema - client.query(`SET search_path TO "${schema}",public;`) - } - }) - - this.pool = pool - this.db = new Kysely({ - dialect: new PostgresDialect({ pool }), - }) - } - - get schema(): string | undefined { - return this.opts.schema - } - - get isTransaction() { - return this.db.isTransaction - } - - assertTransaction() { - assert(this.isTransaction, 'Transaction required') - } - - assertNotTransaction() { - assert(!this.isTransaction, 'Cannot be in a transaction') - } - - asPrimary(): Database { - throw new Error('Primary db required') - } - - async close(): Promise { - if (this.destroyed) return - await this.db.destroy() - this.destroyed = true - } -} - -export default Database - -const onPoolError = (err: Error) => dbLogger.error({ err }, 'db pool error') -const onClientError = (err: Error) => dbLogger.error({ err }, 'db client error') diff --git a/packages/bsky/src/db/index.ts b/packages/bsky/src/db/index.ts deleted file mode 100644 index 1c5886fb10e..00000000000 --- a/packages/bsky/src/db/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './primary' -export * from './db' -export * from './coordinator' diff --git a/packages/bsky/src/db/leader.ts b/packages/bsky/src/db/leader.ts deleted file mode 100644 index ebd44bf98d6..00000000000 --- a/packages/bsky/src/db/leader.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { PoolClient } from 'pg' -import PrimaryDatabase from './primary' - -export class Leader { - session: Session | null = null - constructor(public id: number, public db: PrimaryDatabase) {} - - async run( - task: (ctx: { signal: AbortSignal }) => Promise, - ): Promise> { - const session = await this.lock() - if (!session) return { ran: false } - try { - const result = await task({ signal: session.abortController.signal }) - return { ran: true, result } - } finally { - this.release() - } - } - - destroy(err?: Error) { - this.session?.abortController.abort(err) - } - - private async lock(): Promise { - if (this.session) { - return null - } - - // Postgres implementation uses advisory locking, automatically released by ending connection. - - const client = await this.db.pool.connect() - try { - const lock = await client.query( - 'SELECT pg_try_advisory_lock($1) as acquired', - [this.id], - ) - if (!lock.rows[0].acquired) { - client.release() - return null - } - } catch (err) { - client.release(true) - throw err - } - - const abortController = new AbortController() - client.once('error', (err) => abortController.abort(err)) - this.session = { abortController, client } - return this.session - } - - private release() { - // The flag ensures the connection is destroyed on release, not reused. - // This is required, as that is how the pg advisory lock is released. - this.session?.client.release(true) - this.session = null - } -} - -type Session = { abortController: AbortController; client: PoolClient } - -type RunResult = { ran: false } | { ran: true; result: T } diff --git a/packages/bsky/src/db/migrations/20231205T000257238Z-remove-did-cache.ts b/packages/bsky/src/db/migrations/20231205T000257238Z-remove-did-cache.ts deleted file mode 100644 index 6b57a88bbb9..00000000000 --- a/packages/bsky/src/db/migrations/20231205T000257238Z-remove-did-cache.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Kysely } from 'kysely' - -export async function up(db: Kysely): Promise { - await db.schema.dropTable('did_cache').execute() -} - -export async function down(db: Kysely): Promise { - await db.schema - .createTable('did_cache') - .addColumn('did', 'varchar', (col) => col.primaryKey()) - .addColumn('doc', 'jsonb', (col) => col.notNull()) - .addColumn('updatedAt', 'bigint', (col) => col.notNull()) - .execute() -} diff --git a/packages/bsky/src/db/views.ts b/packages/bsky/src/db/views.ts deleted file mode 100644 index d5aa9941436..00000000000 --- a/packages/bsky/src/db/views.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { jitter, wait } from '@atproto/common' -import { Leader } from './leader' -import { dbLogger } from '../logger' -import { PrimaryDatabase } from '.' - -export const VIEW_MAINTAINER_ID = 1010 -const VIEWS = ['algo_whats_hot_view'] - -export class ViewMaintainer { - leader = new Leader(VIEW_MAINTAINER_ID, this.db) - destroyed = false - - // @NOTE the db must be authed as the owner of the materialized view, per postgres. - constructor(public db: PrimaryDatabase, public intervalSec = 60) {} - - async run() { - while (!this.destroyed) { - try { - const { ran } = await this.leader.run(async ({ signal }) => { - await this.db.maintainMaterializedViews({ - signal, - views: VIEWS, - intervalSec: this.intervalSec, - }) - }) - if (ran && !this.destroyed) { - throw new Error('View maintainer completed, but should be persistent') - } - } catch (err) { - dbLogger.error( - { - err, - views: VIEWS, - intervalSec: this.intervalSec, - lockId: VIEW_MAINTAINER_ID, - }, - 'view maintainer errored', - ) - } - if (!this.destroyed) { - await wait(10000 + jitter(2000)) - } - } - } - - destroy() { - this.destroyed = true - this.leader.destroy() - } -} diff --git a/packages/bsky/src/did-cache.ts b/packages/bsky/src/did-cache.ts deleted file mode 100644 index 9e45d0d8b30..00000000000 --- a/packages/bsky/src/did-cache.ts +++ /dev/null @@ -1,87 +0,0 @@ -import PQueue from 'p-queue' -import { CacheResult, DidCache, DidDocument } from '@atproto/identity' -import { cacheLogger as log } from './logger' -import { Redis } from './redis' - -type CacheOptions = { - staleTTL: number - maxTTL: number -} - -export class DidRedisCache implements DidCache { - public pQueue: PQueue | null // null during teardown - - constructor(public redis: Redis, public opts: CacheOptions) { - this.pQueue = new PQueue() - } - - async cacheDid(did: string, doc: DidDocument): Promise { - const item = JSON.stringify({ - doc, - updatedAt: Date.now(), - }) - await this.redis.set(did, item, this.opts.maxTTL) - } - - async refreshCache( - did: string, - getDoc: () => Promise, - ): Promise { - this.pQueue?.add(async () => { - try { - const doc = await getDoc() - if (doc) { - await this.cacheDid(did, doc) - } else { - await this.clearEntry(did) - } - } catch (err) { - log.error({ did, err }, 'refreshing did cache failed') - } - }) - } - - async checkCache(did: string): Promise { - let got: string | null - try { - got = await this.redis.get(did) - } catch (err) { - got = null - log.error({ did, err }, 'error fetching did from cache') - } - if (!got) return null - const { doc, updatedAt } = JSON.parse(got) as CacheResult - const now = Date.now() - const expired = now > updatedAt + this.opts.maxTTL - const stale = now > updatedAt + this.opts.staleTTL - return { - doc, - updatedAt, - did, - stale, - expired, - } - } - - async clearEntry(did: string): Promise { - await this.redis.del(did) - } - - async clear(): Promise { - throw new Error('Not implemented for redis cache') - } - - async processAll() { - await this.pQueue?.onIdle() - } - - async destroy() { - const pQueue = this.pQueue - this.pQueue = null - pQueue?.pause() - pQueue?.clear() - await pQueue?.onIdle() - } -} - -export default DidRedisCache diff --git a/packages/bsky/src/hydration/actor.ts b/packages/bsky/src/hydration/actor.ts new file mode 100644 index 00000000000..3ed7f1b036e --- /dev/null +++ b/packages/bsky/src/hydration/actor.ts @@ -0,0 +1,149 @@ +import { DataPlaneClient } from '../data-plane/client' +import { Record as ProfileRecord } from '../lexicon/types/app/bsky/actor/profile' +import { + HydrationMap, + parseRecordBytes, + parseString, + safeTakedownRef, +} from './util' + +export type Actor = { + did: string + handle?: string + profile?: ProfileRecord + profileCid?: string + profileTakedownRef?: string + sortedAt?: Date + takedownRef?: string +} + +export type Actors = HydrationMap + +export type ProfileViewerState = { + muted?: boolean + mutedByList?: string + blockedBy?: string + blocking?: string + blockedByList?: string + blockingByList?: string + following?: string + followedBy?: string +} + +export type ProfileViewerStates = HydrationMap + +export type ProfileAgg = { + followers: number + follows: number + posts: number +} + +export type ProfileAggs = HydrationMap + +export class ActorHydrator { + constructor(public dataplane: DataPlaneClient) {} + + async getRepoRevSafe(did: string | null): Promise { + if (!did) return null + try { + const res = await this.dataplane.getLatestRev({ actorDid: did }) + return parseString(res.rev) ?? null + } catch { + return null + } + } + + async getDids(handleOrDids: string[]): Promise<(string | undefined)[]> { + const handles = handleOrDids.filter((actor) => !actor.startsWith('did:')) + const res = handles.length + ? await this.dataplane.getDidsByHandles({ handles }) + : { dids: [] } + const didByHandle = handles.reduce((acc, cur, i) => { + const did = res.dids[i] + if (did && did.length > 0) { + return acc.set(cur, did) + } + return acc + }, new Map() as Map) + return handleOrDids.map((id) => + id.startsWith('did:') ? id : didByHandle.get(id), + ) + } + + async getDidsDefined(handleOrDids: string[]): Promise { + const res = await this.getDids(handleOrDids) + // @ts-ignore + return res.filter((did) => did !== undefined) + } + + async getActors(dids: string[], includeTakedowns = false): Promise { + if (!dids.length) return new HydrationMap() + const res = await this.dataplane.getActors({ dids }) + return dids.reduce((acc, did, i) => { + const actor = res.actors[i] + if ( + !actor.exists || + (actor.takenDown && !includeTakedowns) || + !!actor.tombstonedAt + ) { + return acc.set(did, null) + } + const profile = + includeTakedowns || !actor.profile?.takenDown + ? actor.profile + : undefined + return acc.set(did, { + did, + handle: parseString(actor.handle), + profile: parseRecordBytes(profile?.record), + profileCid: profile?.cid, + profileTakedownRef: safeTakedownRef(profile), + sortedAt: profile?.sortedAt?.toDate(), + takedownRef: safeTakedownRef(actor), + }) + }, new HydrationMap()) + } + + // "naive" because this method does not verify the existence of the list itself + // a later check in the main hydrator will remove list uris that have been deleted or + // repurposed to "curate lists" + async getProfileViewerStatesNaive( + dids: string[], + viewer: string, + ): Promise { + if (!dids.length) return new HydrationMap() + const res = await this.dataplane.getRelationships({ + actorDid: viewer, + targetDids: dids, + }) + return dids.reduce((acc, did, i) => { + const rels = res.relationships[i] + if (viewer === did) { + // ignore self-follows, self-mutes, self-blocks + return acc.set(did, {}) + } + return acc.set(did, { + muted: rels.muted ?? false, + mutedByList: parseString(rels.mutedByList), + blockedBy: parseString(rels.blockedBy), + blocking: parseString(rels.blocking), + blockedByList: parseString(rels.blockedByList), + blockingByList: parseString(rels.blockingByList), + following: parseString(rels.following), + followedBy: parseString(rels.followedBy), + }) + }, new HydrationMap()) + } + + async getProfileAggregates(dids: string[]): Promise { + if (!dids.length) return new HydrationMap() + const counts = await this.dataplane.getCountsForUsers({ dids }) + return dids.reduce((acc, did, i) => { + return acc.set(did, { + followers: counts.followers[i] ?? 0, + follows: counts.following[i] ?? 0, + posts: counts.posts[i] ?? 0, + }) + }, new HydrationMap()) + } +} diff --git a/packages/bsky/src/hydration/feed.ts b/packages/bsky/src/hydration/feed.ts new file mode 100644 index 00000000000..edd29b851e8 --- /dev/null +++ b/packages/bsky/src/hydration/feed.ts @@ -0,0 +1,204 @@ +import { DataPlaneClient } from '../data-plane/client' +import { Record as PostRecord } from '../lexicon/types/app/bsky/feed/post' +import { Record as LikeRecord } from '../lexicon/types/app/bsky/feed/like' +import { Record as RepostRecord } from '../lexicon/types/app/bsky/feed/repost' +import { Record as FeedGenRecord } from '../lexicon/types/app/bsky/feed/generator' +import { Record as ThreadgateRecord } from '../lexicon/types/app/bsky/feed/threadgate' +import { + HydrationMap, + RecordInfo, + parseRecord, + parseString, + split, +} from './util' +import { AtUri } from '@atproto/syntax' +import { ids } from '../lexicon/lexicons' + +export type Post = RecordInfo & { violatesThreadGate: boolean } +export type Posts = HydrationMap + +export type PostViewerState = { + like?: string + repost?: string +} + +export type PostViewerStates = HydrationMap + +export type PostAgg = { + likes: number + replies: number + reposts: number +} + +export type PostAggs = HydrationMap + +export type Like = RecordInfo +export type Likes = HydrationMap + +export type Repost = RecordInfo +export type Reposts = HydrationMap + +export type FeedGenAgg = { + likes: number +} + +export type FeedGenAggs = HydrationMap + +export type FeedGen = RecordInfo +export type FeedGens = HydrationMap + +export type FeedGenViewerState = { + like?: string +} + +export type FeedGenViewerStates = HydrationMap + +export type Threadgate = RecordInfo +export type Threadgates = HydrationMap + +export type ItemRef = { uri: string; cid?: string } + +// @NOTE the feed item types in the protos for author feeds and timelines +// technically have additional fields, not supported by the mock dataplane. +export type FeedItem = { post: ItemRef; repost?: ItemRef } + +export class FeedHydrator { + constructor(public dataplane: DataPlaneClient) {} + + async getPosts( + uris: string[], + includeTakedowns = false, + given = new HydrationMap(), + ): Promise { + const [have, need] = split(uris, (uri) => given.has(uri)) + const base = have.reduce( + (acc, uri) => acc.set(uri, given.get(uri) ?? null), + new HydrationMap(), + ) + if (!need.length) return base + const res = await this.dataplane.getPostRecords({ uris: need }) + return need.reduce((acc, uri, i) => { + const record = parseRecord(res.records[i], includeTakedowns) + const violatesThreadGate = res.meta[i].violatesThreadGate + return acc.set(uri, record ? { ...record, violatesThreadGate } : null) + }, base) + } + + async getPostViewerStates( + refs: ItemRef[], + viewer: string, + ): Promise { + if (!refs.length) return new HydrationMap() + const [likes, reposts] = await Promise.all([ + this.dataplane.getLikesByActorAndSubjects({ + actorDid: viewer, + refs, + }), + this.dataplane.getRepostsByActorAndSubjects({ + actorDid: viewer, + refs, + }), + ]) + return refs.reduce((acc, { uri }, i) => { + return acc.set(uri, { + like: parseString(likes.uris[i]), + repost: parseString(reposts.uris[i]), + }) + }, new HydrationMap()) + } + + async getPostAggregates(refs: ItemRef[]): Promise { + if (!refs.length) return new HydrationMap() + const counts = await this.dataplane.getInteractionCounts({ refs }) + return refs.reduce((acc, { uri }, i) => { + return acc.set(uri, { + likes: counts.likes[i] ?? 0, + reposts: counts.reposts[i] ?? 0, + replies: counts.replies[i] ?? 0, + }) + }, new HydrationMap()) + } + + async getFeedGens( + uris: string[], + includeTakedowns = false, + ): Promise { + if (!uris.length) return new HydrationMap() + const res = await this.dataplane.getFeedGeneratorRecords({ uris }) + return uris.reduce((acc, uri, i) => { + const record = parseRecord( + res.records[i], + includeTakedowns, + ) + return acc.set(uri, record ?? null) + }, new HydrationMap()) + } + + async getFeedGenViewerStates( + uris: string[], + viewer: string, + ): Promise { + if (!uris.length) return new HydrationMap() + const likes = await this.dataplane.getLikesByActorAndSubjects({ + actorDid: viewer, + refs: uris.map((uri) => ({ uri })), + }) + return uris.reduce((acc, uri, i) => { + return acc.set(uri, { + like: parseString(likes.uris[i]), + }) + }, new HydrationMap()) + } + + async getFeedGenAggregates(refs: ItemRef[]): Promise { + if (!refs.length) return new HydrationMap() + const counts = await this.dataplane.getInteractionCounts({ refs }) + return refs.reduce((acc, { uri }, i) => { + return acc.set(uri, { + likes: counts.likes[i] ?? 0, + }) + }, new HydrationMap()) + } + + async getThreadgatesForPosts( + postUris: string[], + includeTakedowns = false, + ): Promise { + if (!postUris.length) return new HydrationMap() + const uris = postUris.map((uri) => { + const parsed = new AtUri(uri) + return AtUri.make( + parsed.hostname, + ids.AppBskyFeedThreadgate, + parsed.rkey, + ).toString() + }) + const res = await this.dataplane.getThreadGateRecords({ uris }) + return uris.reduce((acc, uri, i) => { + const record = parseRecord( + res.records[i], + includeTakedowns, + ) + return acc.set(uri, record ?? null) + }, new HydrationMap()) + } + + // @TODO may not be supported yet by data plane + async getLikes(uris: string[], includeTakedowns = false): Promise { + if (!uris.length) return new HydrationMap() + const res = await this.dataplane.getLikeRecords({ uris }) + return uris.reduce((acc, uri, i) => { + const record = parseRecord(res.records[i], includeTakedowns) + return acc.set(uri, record ?? null) + }, new HydrationMap()) + } + + async getReposts(uris: string[], includeTakedowns = false): Promise { + if (!uris.length) return new HydrationMap() + const res = await this.dataplane.getRepostRecords({ uris }) + return uris.reduce((acc, uri, i) => { + const record = parseRecord(res.records[i], includeTakedowns) + return acc.set(uri, record ?? null) + }, new HydrationMap()) + } +} diff --git a/packages/bsky/src/hydration/graph.ts b/packages/bsky/src/hydration/graph.ts new file mode 100644 index 00000000000..efcd2fb9948 --- /dev/null +++ b/packages/bsky/src/hydration/graph.ts @@ -0,0 +1,195 @@ +import { Record as FollowRecord } from '../lexicon/types/app/bsky/graph/follow' +import { Record as BlockRecord } from '../lexicon/types/app/bsky/graph/block' +import { Record as ListRecord } from '../lexicon/types/app/bsky/graph/list' +import { Record as ListItemRecord } from '../lexicon/types/app/bsky/graph/listitem' +import { DataPlaneClient } from '../data-plane/client' +import { HydrationMap, RecordInfo, parseRecord } from './util' +import { FollowInfo } from '../proto/bsky_pb' + +export type List = RecordInfo +export type Lists = HydrationMap + +export type ListItem = RecordInfo +export type ListItems = HydrationMap + +export type ListViewerState = { + viewerMuted?: string + viewerListBlockUri?: string + viewerInList?: string +} + +export type ListViewerStates = HydrationMap + +export type Follow = RecordInfo +export type Follows = HydrationMap + +export type Block = RecordInfo + +export type RelationshipPair = [didA: string, didB: string] + +const dedupePairs = (pairs: RelationshipPair[]): RelationshipPair[] => { + const mapped = pairs.reduce((acc, cur) => { + const sorted = ([...cur] as RelationshipPair).sort() + acc[sorted.join('-')] = sorted + return acc + }, {} as Record) + return Object.values(mapped) +} + +export class Blocks { + _blocks: Map = new Map() + constructor() {} + + static key(didA: string, didB: string): string { + return [didA, didB].sort().join(',') + } + + set(didA: string, didB: string, exists: boolean): Blocks { + const key = Blocks.key(didA, didB) + this._blocks.set(key, exists) + return this + } + + has(didA: string, didB: string): boolean { + const key = Blocks.key(didA, didB) + return this._blocks.has(key) + } + + isBlocked(didA: string, didB: string): boolean { + if (didA === didB) return false // ignore self-blocks + const key = Blocks.key(didA, didB) + return this._blocks.get(key) ?? false + } + + merge(blocks: Blocks): Blocks { + blocks._blocks.forEach((exists, key) => { + this._blocks.set(key, exists) + }) + return this + } +} + +export class GraphHydrator { + constructor(public dataplane: DataPlaneClient) {} + + async getLists(uris: string[], includeTakedowns = false): Promise { + if (!uris.length) return new HydrationMap() + const res = await this.dataplane.getListRecords({ uris }) + return uris.reduce((acc, uri, i) => { + const record = parseRecord(res.records[i], includeTakedowns) + return acc.set(uri, record ?? null) + }, new HydrationMap()) + } + + // @TODO may not be supported yet by data plane + async getListItems( + uris: string[], + includeTakedowns = false, + ): Promise { + if (!uris.length) return new HydrationMap() + const res = await this.dataplane.getListItemRecords({ uris }) + return uris.reduce((acc, uri, i) => { + const record = parseRecord( + res.records[i], + includeTakedowns, + ) + return acc.set(uri, record ?? null) + }, new HydrationMap()) + } + + async getListViewerStates( + uris: string[], + viewer: string, + ): Promise { + if (!uris.length) return new HydrationMap() + const mutesAndBlocks = await Promise.all( + uris.map((uri) => this.getMutesAndBlocks(uri, viewer)), + ) + const listMemberships = await this.dataplane.getListMembership({ + actorDid: viewer, + listUris: uris, + }) + return uris.reduce((acc, uri, i) => { + return acc.set(uri, { + viewerMuted: mutesAndBlocks[i].muted ? uri : undefined, + viewerListBlockUri: mutesAndBlocks[i].listBlockUri || undefined, + viewerInList: listMemberships.listitemUris[i], + }) + }, new HydrationMap()) + } + + private async getMutesAndBlocks(uri: string, viewer: string) { + const [muted, listBlockUri] = await Promise.all([ + this.dataplane.getMutelistSubscription({ + actorDid: viewer, + listUri: uri, + }), + this.dataplane.getBlocklistSubscription({ + actorDid: viewer, + listUri: uri, + }), + ]) + return { + muted: muted.subscribed, + listBlockUri: listBlockUri.listblockUri, + } + } + + async getBidirectionalBlocks(pairs: RelationshipPair[]): Promise { + if (!pairs.length) return new Blocks() + const deduped = dedupePairs(pairs).map(([a, b]) => ({ a, b })) + const res = await this.dataplane.getBlockExistence({ pairs: deduped }) + const blocks = new Blocks() + for (let i = 0; i < deduped.length; i++) { + const pair = deduped[i] + blocks.set(pair.a, pair.b, res.exists[i] ?? false) + } + return blocks + } + + async getFollows(uris: string[], includeTakedowns = false): Promise { + if (!uris.length) return new HydrationMap() + const res = await this.dataplane.getFollowRecords({ uris }) + return uris.reduce((acc, uri, i) => { + const record = parseRecord(res.records[i], includeTakedowns) + return acc.set(uri, record ?? null) + }, new HydrationMap()) + } + + async getBlocks(uris: string[], includeTakedowns = false): Promise { + if (!uris.length) return new HydrationMap() + const res = await this.dataplane.getBlockRecords({ uris }) + return uris.reduce((acc, uri, i) => { + const record = parseRecord(res.records[i], includeTakedowns) + return acc.set(uri, record ?? null) + }, new HydrationMap()) + } + + async getActorFollows(input: { + did: string + cursor?: string + limit?: number + }): Promise<{ follows: FollowInfo[]; cursor: string }> { + const { did, cursor, limit } = input + const res = await this.dataplane.getFollows({ + actorDid: did, + cursor, + limit, + }) + return { follows: res.follows, cursor: res.cursor } + } + + async getActorFollowers(input: { + did: string + cursor?: string + limit?: number + }): Promise<{ followers: FollowInfo[]; cursor: string }> { + const { did, cursor, limit } = input + const res = await this.dataplane.getFollowers({ + actorDid: did, + cursor, + limit, + }) + return { followers: res.followers, cursor: res.cursor } + } +} diff --git a/packages/bsky/src/hydration/hydrator.ts b/packages/bsky/src/hydration/hydrator.ts new file mode 100644 index 00000000000..c79df963a7b --- /dev/null +++ b/packages/bsky/src/hydration/hydrator.ts @@ -0,0 +1,726 @@ +import assert from 'assert' +import { mapDefined } from '@atproto/common' +import { AtUri } from '@atproto/syntax' +import { DataPlaneClient } from '../data-plane/client' +import { Notification } from '../proto/bsky_pb' +import { ids } from '../lexicon/lexicons' +import { isMain as isEmbedRecord } from '../lexicon/types/app/bsky/embed/record' +import { isMain as isEmbedRecordWithMedia } from '../lexicon/types/app/bsky/embed/recordWithMedia' +import { isListRule } from '../lexicon/types/app/bsky/feed/threadgate' +import { + ActorHydrator, + ProfileAggs, + Actors, + ProfileViewerStates, + ProfileViewerState, +} from './actor' +import { + Follows, + GraphHydrator, + ListItems, + ListViewerStates, + Lists, + RelationshipPair, +} from './graph' +import { LabelHydrator, Labels } from './label' +import { HydrationMap, RecordInfo, didFromUri, urisByCollection } from './util' +import { + FeedGenAggs, + FeedGens, + FeedGenViewerStates, + FeedHydrator, + Likes, + Post, + Posts, + Reposts, + PostAggs, + PostViewerStates, + Threadgates, + FeedItem, + ItemRef, +} from './feed' + +export type HydrationState = { + viewer?: string | null + actors?: Actors + profileViewers?: ProfileViewerStates + profileAggs?: ProfileAggs + posts?: Posts + postAggs?: PostAggs + postViewers?: PostViewerStates + postBlocks?: PostBlocks + reposts?: Reposts + follows?: Follows + followBlocks?: FollowBlocks + threadgates?: Threadgates + lists?: Lists + listViewers?: ListViewerStates + listItems?: ListItems + likes?: Likes + labels?: Labels + feedgens?: FeedGens + feedgenViewers?: FeedGenViewerStates + feedgenAggs?: FeedGenAggs +} + +export type PostBlock = { embed: boolean; reply: boolean } +export type PostBlocks = HydrationMap +type PostBlockPairs = { embed?: RelationshipPair; reply?: RelationshipPair } + +export type FollowBlock = boolean +export type FollowBlocks = HydrationMap + +export class Hydrator { + actor: ActorHydrator + feed: FeedHydrator + graph: GraphHydrator + label: LabelHydrator + + constructor( + public dataplane: DataPlaneClient, + public opts?: { labelsFromIssuerDids?: string[] }, + ) { + this.actor = new ActorHydrator(dataplane) + this.feed = new FeedHydrator(dataplane) + this.graph = new GraphHydrator(dataplane) + this.label = new LabelHydrator(dataplane, opts) + } + + // app.bsky.actor.defs#profileView + // - profile viewer + // - list basic + // Note: builds on the naive profile viewer hydrator and removes references to lists that have been deleted + async hydrateProfileViewers( + dids: string[], + viewer: string, + ): Promise { + const profileViewers = await this.actor.getProfileViewerStatesNaive( + dids, + viewer, + ) + const listUris: string[] = [] + profileViewers?.forEach((item) => { + listUris.push(...listUrisFromProfileViewer(item)) + }) + const listState = await this.hydrateListsBasic(listUris, viewer) + // if a list no longer exists or is not a mod list, then remove from viewer state + profileViewers?.forEach((item) => { + removeNonModListsFromProfileViewer(item, listState) + }) + return mergeStates(listState, { + profileViewers, + viewer, + }) + } + + // app.bsky.actor.defs#profileView + // - profile + // - list basic + async hydrateProfiles( + dids: string[], + viewer: string | null, + includeTakedowns = false, + ): Promise { + const [actors, labels, profileViewersState] = await Promise.all([ + this.actor.getActors(dids, includeTakedowns), + this.label.getLabelsForSubjects(labelSubjectsForDid(dids)), + viewer ? this.hydrateProfileViewers(dids, viewer) : undefined, + ]) + return mergeStates(profileViewersState ?? {}, { + actors, + labels, + viewer, + }) + } + + // app.bsky.actor.defs#profileViewBasic + // - profile basic + // - profile + // - list basic + async hydrateProfilesBasic( + dids: string[], + viewer: string | null, + includeTakedowns = false, + ): Promise { + return this.hydrateProfiles(dids, viewer, includeTakedowns) + } + + // app.bsky.actor.defs#profileViewDetailed + // - profile detailed + // - profile + // - list basic + async hydrateProfilesDetailed( + dids: string[], + viewer: string | null, + includeTakedowns = false, + ): Promise { + const [state, profileAggs] = await Promise.all([ + this.hydrateProfiles(dids, viewer, includeTakedowns), + this.actor.getProfileAggregates(dids), + ]) + return { + ...state, + profileAggs, + } + } + + // app.bsky.graph.defs#listView + // - list + // - profile basic + async hydrateLists( + uris: string[], + viewer: string | null, + ): Promise { + const [listsState, profilesState] = await Promise.all([ + await this.hydrateListsBasic(uris, viewer), + await this.hydrateProfilesBasic(uris.map(didFromUri), viewer), + ]) + return mergeStates(listsState, profilesState) + } + + // app.bsky.graph.defs#listViewBasic + // - list basic + async hydrateListsBasic( + uris: string[], + viewer: string | null, + ): Promise { + const [lists, listViewers] = await Promise.all([ + this.graph.getLists(uris), + viewer ? this.graph.getListViewerStates(uris, viewer) : undefined, + ]) + return { lists, listViewers, viewer } + } + + // app.bsky.graph.defs#listItemView + // - list item + // - profile + // - list basic + async hydrateListItems( + uris: string[], + viewer: string | null, + ): Promise { + const listItems = await this.graph.getListItems(uris) + const dids: string[] = [] + listItems.forEach((item) => { + if (item) { + dids.push(item.record.subject) + } + }) + const profileState = await this.hydrateProfiles(dids, viewer) + return mergeStates(profileState, { listItems, viewer }) + } + + // app.bsky.feed.defs#postView + // - post + // - profile + // - list basic + // - list + // - profile + // - list basic + // - feedgen + // - profile + // - list basic + async hydratePosts( + refs: ItemRef[], + viewer: string | null, + includeTakedowns = false, + state: HydrationState = {}, + ): Promise { + const uris = refs.map((ref) => ref.uri) + const postsLayer0 = await this.feed.getPosts( + uris, + includeTakedowns, + state.posts, + ) + // first level embeds plus thread roots we haven't fetched yet + const urisLayer1 = nestedRecordUrisFromPosts(postsLayer0) + const additionalRootUris = rootUrisFromPosts(postsLayer0) // supports computing threadgates + const urisLayer1ByCollection = urisByCollection(urisLayer1) + const postUrisLayer1 = urisLayer1ByCollection.get(ids.AppBskyFeedPost) ?? [] + const postsLayer1 = await this.feed.getPosts( + [...postUrisLayer1, ...additionalRootUris], + includeTakedowns, + ) + // second level embeds, ignoring any additional root uris we mixed-in to the previous layer + const urisLayer2 = nestedRecordUrisFromPosts(postsLayer1, postUrisLayer1) + const urisLayer2ByCollection = urisByCollection(urisLayer2) + const postUrisLayer2 = urisLayer2ByCollection.get(ids.AppBskyFeedPost) ?? [] + const threadRootUris = new Set() + for (const [uri, post] of postsLayer0) { + if (post) { + threadRootUris.add(rootUriFromPost(post) ?? uri) + } + } + const [postsLayer2, threadgates] = await Promise.all([ + this.feed.getPosts(postUrisLayer2, includeTakedowns), + this.feed.getThreadgatesForPosts([...threadRootUris.values()]), + ]) + // collect list/feedgen embeds, lists in threadgates, post record hydration + const gateListUris = getListUrisFromGates(threadgates) + const nestedListUris = [ + ...(urisLayer1ByCollection.get(ids.AppBskyGraphList) ?? []), + ...(urisLayer2ByCollection.get(ids.AppBskyGraphList) ?? []), + ] + const nestedFeedGenUris = [ + ...(urisLayer1ByCollection.get(ids.AppBskyFeedGenerator) ?? []), + ...(urisLayer2ByCollection.get(ids.AppBskyFeedGenerator) ?? []), + ] + const posts = + mergeManyMaps(postsLayer0, postsLayer1, postsLayer2) ?? postsLayer0 + const allPostUris = [...posts.keys()] + const [ + postAggs, + postViewers, + labels, + postBlocks, + profileState, + listState, + feedGenState, + ] = await Promise.all([ + this.feed.getPostAggregates(refs), + viewer ? this.feed.getPostViewerStates(refs, viewer) : undefined, + this.label.getLabelsForSubjects(allPostUris), + this.hydratePostBlocks(posts), + this.hydrateProfiles( + allPostUris.map(didFromUri), + viewer, + includeTakedowns, + ), + this.hydrateLists([...nestedListUris, ...gateListUris], viewer), + this.hydrateFeedGens(nestedFeedGenUris, viewer), + ]) + // combine all hydration state + return mergeManyStates(profileState, listState, feedGenState, { + posts, + postAggs, + postViewers, + postBlocks, + labels, + threadgates, + viewer, + }) + } + + private async hydratePostBlocks(posts: Posts): Promise { + const postBlocks = new HydrationMap() + const postBlocksPairs = new Map() + const relationships: RelationshipPair[] = [] + for (const [uri, item] of posts) { + if (!item) continue + const post = item.record + const creator = didFromUri(uri) + const postBlockPairs: PostBlockPairs = {} + postBlocksPairs.set(uri, postBlockPairs) + // 3p block for replies + const parentUri = post.reply?.parent.uri + const parentDid = parentUri && didFromUri(parentUri) + if (parentDid) { + const pair: RelationshipPair = [creator, parentDid] + relationships.push(pair) + postBlockPairs.reply = pair + } + // 3p block for record embeds + for (const embedUri of nestedRecordUris(post)) { + const pair: RelationshipPair = [creator, didFromUri(embedUri)] + relationships.push(pair) + postBlockPairs.embed = pair + } + } + // replace embed/reply pairs with block state + const blocks = await this.graph.getBidirectionalBlocks(relationships) + for (const [uri, { embed, reply }] of postBlocksPairs) { + postBlocks.set(uri, { + embed: !!embed && blocks.isBlocked(...embed), + reply: !!reply && blocks.isBlocked(...reply), + }) + } + return postBlocks + } + + // app.bsky.feed.defs#feedViewPost + // - post (+ replies) + // - profile + // - list basic + // - list + // - profile + // - list basic + // - feedgen + // - profile + // - list basic + // - repost + // - profile + // - list basic + // - post + // - ... + async hydrateFeedItems( + items: FeedItem[], + viewer: string | null, + includeTakedowns = false, + ): Promise { + const postUris = items.map((item) => item.post.uri) + const repostUris = mapDefined(items, (item) => item.repost?.uri) + const [posts, reposts, repostProfileState] = await Promise.all([ + this.feed.getPosts(postUris, includeTakedowns), + this.feed.getReposts(repostUris, includeTakedowns), + this.hydrateProfiles( + repostUris.map(didFromUri), + viewer, + includeTakedowns, + ), + ]) + const postAndReplyRefs: ItemRef[] = [] + posts.forEach((post, uri) => { + if (!post) return + postAndReplyRefs.push({ uri, cid: post.cid }) + if (post.record.reply) { + postAndReplyRefs.push(post.record.reply.root, post.record.reply.parent) + } + }) + const postState = await this.hydratePosts( + postAndReplyRefs, + viewer, + includeTakedowns, + { posts }, + ) + return mergeManyStates(postState, repostProfileState, { + reposts, + viewer, + }) + } + + // app.bsky.feed.defs#threadViewPost + // - post + // - profile + // - list basic + // - list + // - profile + // - list basic + // - feedgen + // - profile + // - list basic + async hydrateThreadPosts( + refs: ItemRef[], + viewer: string | null, + ): Promise { + return this.hydratePosts(refs, viewer) + } + + // app.bsky.feed.defs#generatorView + // - feedgen + // - profile + // - list basic + async hydrateFeedGens( + uris: string[], // @TODO any way to get refs here? + viewer: string | null, + ): Promise { + const [feedgens, feedgenAggs, feedgenViewers, profileState] = + await Promise.all([ + this.feed.getFeedGens(uris), + this.feed.getFeedGenAggregates(uris.map((uri) => ({ uri }))), + viewer ? this.feed.getFeedGenViewerStates(uris, viewer) : undefined, + this.hydrateProfiles(uris.map(didFromUri), viewer), + ]) + return mergeStates(profileState, { + feedgens, + feedgenAggs, + feedgenViewers, + viewer, + }) + } + + // app.bsky.feed.getLikes#like + // - like + // - profile + // - list basic + async hydrateLikes( + uris: string[], + viewer: string | null, + ): Promise { + const [likes, profileState] = await Promise.all([ + this.feed.getLikes(uris), + this.hydrateProfiles(uris.map(didFromUri), viewer), + ]) + return mergeStates(profileState, { likes, viewer }) + } + + // app.bsky.feed.getRepostedBy#repostedBy + // - repost + // - profile + // - list basic + async hydrateReposts(uris: string[], viewer: string | null) { + const [reposts, profileState] = await Promise.all([ + this.feed.getReposts(uris), + this.hydrateProfiles(uris.map(didFromUri), viewer), + ]) + return mergeStates(profileState, { reposts, viewer }) + } + + // app.bsky.notification.listNotifications#notification + // - notification + // - profile + // - list basic + async hydrateNotifications( + notifs: Notification[], + viewer: string | null, + ): Promise { + const uris = notifs.map((notif) => notif.uri) + const collections = urisByCollection(uris) + const postUris = collections.get(ids.AppBskyFeedPost) ?? [] + const likeUris = collections.get(ids.AppBskyFeedLike) ?? [] + const repostUris = collections.get(ids.AppBskyFeedRepost) ?? [] + const followUris = collections.get(ids.AppBskyGraphFollow) ?? [] + const [posts, likes, reposts, follows, labels, profileState] = + await Promise.all([ + this.feed.getPosts(postUris), // reason: mention, reply, quote + this.feed.getLikes(likeUris), // reason: like + this.feed.getReposts(repostUris), // reason: repost + this.graph.getFollows(followUris), // reason: follow + this.label.getLabelsForSubjects(uris), + this.hydrateProfiles(uris.map(didFromUri), viewer), + ]) + return mergeStates(profileState, { + posts, + likes, + reposts, + follows, + labels, + viewer, + }) + } + + // provides partial hydration state withing getFollows / getFollowers, mainly for applying rules + async hydrateFollows(uris: string[]): Promise { + const follows = await this.graph.getFollows(uris) + const pairs: RelationshipPair[] = [] + for (const [uri, follow] of follows) { + if (follow) { + pairs.push([didFromUri(uri), follow.record.subject]) + } + } + const blocks = await this.graph.getBidirectionalBlocks(pairs) + const followBlocks = new HydrationMap() + for (const [uri, follow] of follows) { + if (follow) { + followBlocks.set( + uri, + blocks.isBlocked(didFromUri(uri), follow.record.subject), + ) + } else { + followBlocks.set(uri, null) + } + } + return { follows, followBlocks } + } + + // ad-hoc record hydration + // in com.atproto.repo.getRecord + async getRecord( + uri: string, + includeTakedowns = false, + ): Promise> | undefined> { + const parsed = new AtUri(uri) + const collection = parsed.collection + if (collection === ids.AppBskyFeedPost) { + return ( + (await this.feed.getPosts([uri], includeTakedowns)).get(uri) ?? + undefined + ) + } else if (collection === ids.AppBskyFeedRepost) { + return ( + (await this.feed.getReposts([uri], includeTakedowns)).get(uri) ?? + undefined + ) + } else if (collection === ids.AppBskyFeedLike) { + return ( + (await this.feed.getLikes([uri], includeTakedowns)).get(uri) ?? + undefined + ) + } else if (collection === ids.AppBskyGraphFollow) { + return ( + (await this.graph.getFollows([uri], includeTakedowns)).get(uri) ?? + undefined + ) + } else if (collection === ids.AppBskyGraphList) { + return ( + (await this.graph.getLists([uri], includeTakedowns)).get(uri) ?? + undefined + ) + } else if (collection === ids.AppBskyGraphListitem) { + return ( + (await this.graph.getListItems([uri], includeTakedowns)).get(uri) ?? + undefined + ) + } else if (collection === ids.AppBskyGraphBlock) { + return ( + (await this.graph.getBlocks([uri], includeTakedowns)).get(uri) ?? + undefined + ) + } else if (collection === ids.AppBskyFeedGenerator) { + return ( + (await this.feed.getFeedGens([uri], includeTakedowns)).get(uri) ?? + undefined + ) + } else if (collection === ids.AppBskyActorProfile) { + const did = parsed.hostname + const actor = (await this.actor.getActors([did], includeTakedowns)).get( + did, + ) + if (!actor?.profile || !actor?.profileCid) return undefined + return { + record: actor.profile, + cid: actor.profileCid, + sortedAt: actor.sortedAt ?? new Date(0), // @NOTE will be present since profile record is present + takedownRef: actor.profileTakedownRef, + } + } + } +} + +const listUrisFromProfileViewer = (item: ProfileViewerState | null) => { + const listUris: string[] = [] + if (item?.mutedByList) { + listUris.push(item.mutedByList) + } + if (item?.blockingByList) { + listUris.push(item.blockingByList) + } + // blocked-by list does not appear in views, but will be used to evaluate the existence of a block between users. + if (item?.blockedByList) { + listUris.push(item.blockedByList) + } + return listUris +} + +const removeNonModListsFromProfileViewer = ( + item: ProfileViewerState | null, + state: HydrationState, +) => { + if (!isModList(item?.mutedByList, state)) { + delete item?.mutedByList + } + if (!isModList(item?.blockingByList, state)) { + delete item?.blockingByList + } + if (!isModList(item?.blockedByList, state)) { + delete item?.blockedByList + } +} + +const isModList = ( + listUri: string | undefined, + state: HydrationState, +): boolean => { + if (!listUri) return false + const list = state.lists?.get(listUri) + return list?.record.purpose === 'app.bsky.graph.defs#modlist' +} + +const labelSubjectsForDid = (dids: string[]) => { + return [ + ...dids, + ...dids.map((did) => + AtUri.make(did, ids.AppBskyActorProfile, 'self').toString(), + ), + ] +} + +const rootUrisFromPosts = (posts: Posts): string[] => { + const uris: string[] = [] + for (const item of posts.values()) { + const rootUri = item && rootUriFromPost(item) + if (rootUri) { + uris.push(rootUri) + } + } + return uris +} + +const rootUriFromPost = (post: Post): string | undefined => { + return post.record.reply?.root.uri +} + +const nestedRecordUrisFromPosts = ( + posts: Posts, + fromUris?: string[], +): string[] => { + const uris: string[] = [] + const postUris = fromUris ?? posts.keys() + for (const uri of postUris) { + const item = posts.get(uri) + if (item) { + uris.push(...nestedRecordUris(item.record)) + } + } + return uris +} + +const nestedRecordUris = (post: Post['record']): string[] => { + const uris: string[] = [] + if (!post?.embed) return uris + if (isEmbedRecord(post.embed)) { + uris.push(post.embed.record.uri) + } else if (isEmbedRecordWithMedia(post.embed)) { + uris.push(post.embed.record.record.uri) + } + return uris +} + +const getListUrisFromGates = (gates: Threadgates) => { + const uris: string[] = [] + for (const gate of gates.values()) { + const listRules = gate?.record.allow?.filter(isListRule) ?? [] + for (const rule of listRules) { + uris.push(rule.list) + } + } + return uris +} + +export const mergeStates = ( + stateA: HydrationState, + stateB: HydrationState, +): HydrationState => { + assert( + !stateA.viewer || !stateB.viewer || stateA.viewer === stateB.viewer, + 'incompatible viewers', + ) + return { + viewer: stateA.viewer ?? stateB.viewer, + actors: mergeMaps(stateA.actors, stateB.actors), + profileAggs: mergeMaps(stateA.profileAggs, stateB.profileAggs), + profileViewers: mergeMaps(stateA.profileViewers, stateB.profileViewers), + posts: mergeMaps(stateA.posts, stateB.posts), + postAggs: mergeMaps(stateA.postAggs, stateB.postAggs), + postViewers: mergeMaps(stateA.postViewers, stateB.postViewers), + postBlocks: mergeMaps(stateA.postBlocks, stateB.postBlocks), + reposts: mergeMaps(stateA.reposts, stateB.reposts), + follows: mergeMaps(stateA.follows, stateB.follows), + followBlocks: mergeMaps(stateA.followBlocks, stateB.followBlocks), + threadgates: mergeMaps(stateA.threadgates, stateB.threadgates), + lists: mergeMaps(stateA.lists, stateB.lists), + listViewers: mergeMaps(stateA.listViewers, stateB.listViewers), + listItems: mergeMaps(stateA.listItems, stateB.listItems), + likes: mergeMaps(stateA.likes, stateB.likes), + labels: mergeMaps(stateA.labels, stateB.labels), + feedgens: mergeMaps(stateA.feedgens, stateB.feedgens), + feedgenAggs: mergeMaps(stateA.feedgenAggs, stateB.feedgenAggs), + feedgenViewers: mergeMaps(stateA.feedgenViewers, stateB.feedgenViewers), + } +} + +const mergeMaps = ( + mapA?: HydrationMap, + mapB?: HydrationMap, +): HydrationMap | undefined => { + if (!mapA) return mapB + if (!mapB) return mapA + return mapA.merge(mapB) +} + +const mergeManyStates = (...states: HydrationState[]) => { + return states.reduce(mergeStates, {} as HydrationState) +} + +const mergeManyMaps = (...maps: HydrationMap[]) => { + return maps.reduce(mergeMaps, undefined as HydrationMap | undefined) +} diff --git a/packages/bsky/src/hydration/label.ts b/packages/bsky/src/hydration/label.ts new file mode 100644 index 00000000000..352c9ed4059 --- /dev/null +++ b/packages/bsky/src/hydration/label.ts @@ -0,0 +1,36 @@ +import { DataPlaneClient } from '../data-plane/client' +import { Label } from '../lexicon/types/com/atproto/label/defs' +import { HydrationMap, parseJsonBytes } from './util' + +export type { Label } from '../lexicon/types/com/atproto/label/defs' + +export type Labels = HydrationMap + +export class LabelHydrator { + constructor( + public dataplane: DataPlaneClient, + public opts?: { labelsFromIssuerDids?: string[] }, + ) {} + + async getLabelsForSubjects( + subjects: string[], + issuers?: string[], + ): Promise { + issuers = ([] as string[]) + .concat(issuers ?? []) + .concat(this.opts?.labelsFromIssuerDids ?? []) + if (!subjects.length || !issuers.length) return new HydrationMap() + const res = await this.dataplane.getLabels({ subjects, issuers }) + return res.labels.reduce((acc, cur) => { + const label = parseJsonBytes(cur) as Label | undefined + if (!label || label.neg) return acc + const entry = acc.get(label.uri) + if (entry) { + entry.push(label) + } else { + acc.set(label.uri, [label]) + } + return acc + }, new HydrationMap()) + } +} diff --git a/packages/bsky/src/hydration/util.ts b/packages/bsky/src/hydration/util.ts new file mode 100644 index 00000000000..675c0e9f3d0 --- /dev/null +++ b/packages/bsky/src/hydration/util.ts @@ -0,0 +1,125 @@ +import { AtUri } from '@atproto/syntax' +import { jsonToLex } from '@atproto/lexicon' +import { CID } from 'multiformats/cid' +import * as ui8 from 'uint8arrays' +import { lexicons } from '../lexicon/lexicons' +import { Record } from '../proto/bsky_pb' + +export class HydrationMap extends Map { + merge(map: HydrationMap): HydrationMap { + map.forEach((val, key) => { + this.set(key, val) + }) + return this + } +} + +export type RecordInfo = { + record: T + cid: string + sortedAt: Date + takedownRef: string | undefined +} + +export const parseRecord = ( + entry: Record, + includeTakedowns: boolean, +): RecordInfo | undefined => { + if (!includeTakedowns && entry.takenDown) { + return undefined + } + const record = parseRecordBytes(entry.record) + const cid = entry.cid + const sortedAt = entry.sortedAt?.toDate() ?? new Date(0) + if (!record || !cid) return + if (!isValidRecord(record)) { + return + } + return { + record, + cid, + sortedAt, + takedownRef: safeTakedownRef(entry), + } +} + +const isValidRecord = (json: unknown) => { + const lexRecord = jsonToLex(json) + if (typeof lexRecord?.['$type'] !== 'string') { + return false + } + try { + lexicons.assertValidRecord(lexRecord['$type'], lexRecord) + return true + } catch { + return false + } +} + +// @NOTE not parsed into lex format, so will not match lexicon record types on CID and blob values. +export const parseRecordBytes = ( + bytes: Uint8Array | undefined, +): T | undefined => { + return parseJsonBytes(bytes) as T +} + +export const parseJsonBytes = ( + bytes: Uint8Array | undefined, +): JSON | undefined => { + if (!bytes || bytes.byteLength === 0) return + const parsed = JSON.parse(ui8.toString(bytes, 'utf8')) + return parsed ?? undefined +} + +export const parseString = (str: string | undefined): string | undefined => { + return str && str.length > 0 ? str : undefined +} + +export const parseCid = (cidStr: string | undefined): CID | undefined => { + if (!cidStr || cidStr.length === 0) return + try { + return CID.parse(cidStr) + } catch { + return + } +} + +export const didFromUri = (uri: string) => { + return new AtUri(uri).hostname +} + +export const urisByCollection = (uris: string[]): Map => { + const result = new Map() + for (const uri of uris) { + const collection = new AtUri(uri).collection + const items = result.get(collection) ?? [] + items.push(uri) + result.set(collection, items) + } + return result +} + +export const split = ( + items: T[], + predicate: (item: T) => boolean, +): [T[], T[]] => { + const yes: T[] = [] + const no: T[] = [] + for (const item of items) { + if (predicate(item)) { + yes.push(item) + } else { + no.push(item) + } + } + return [yes, no] +} + +export const safeTakedownRef = (obj?: { + takenDown: boolean + takedownRef: string +}): string | undefined => { + if (!obj) return + if (obj.takedownRef) return obj.takedownRef + if (obj.takenDown) return 'BSKY-TAKEDOWN-UNKNOWN' +} diff --git a/packages/bsky/src/image/uri.ts b/packages/bsky/src/image/uri.ts index 5e288e29d10..3de769178ed 100644 --- a/packages/bsky/src/image/uri.ts +++ b/packages/bsky/src/image/uri.ts @@ -1,4 +1,3 @@ -import { CID } from 'multiformats/cid' import { Options } from './util' // @NOTE if there are any additions here, ensure to include them on ImageUriBuilder.presets @@ -20,7 +19,7 @@ export class ImageUriBuilder { 'feed_fullsize', ] - getPresetUri(id: ImagePreset, did: string, cid: string | CID): string { + getPresetUri(id: ImagePreset, did: string, cid: string): string { const options = presets[id] if (!options) { throw new Error(`Unrecognized requested common uri type: ${id}`) @@ -30,14 +29,14 @@ export class ImageUriBuilder { ImageUriBuilder.getPath({ preset: id, did, - cid: typeof cid === 'string' ? CID.parse(cid) : cid, + cid, }) ) } static getPath(opts: { preset: ImagePreset } & BlobLocation) { const { format } = presets[opts.preset] - return `/${opts.preset}/plain/${opts.did}/${opts.cid.toString()}@${format}` + return `/${opts.preset}/plain/${opts.did}/${opts.cid}@${format}` } static getOptions( @@ -59,14 +58,14 @@ export class ImageUriBuilder { return { ...presets[preset], did, - cid: CID.parse(cid), + cid, preset, format, } } } -type BlobLocation = { cid: CID; did: string } +type BlobLocation = { cid: string; did: string } export class BadPathError extends Error {} diff --git a/packages/bsky/src/index.ts b/packages/bsky/src/index.ts index 8a6bb6592e6..a968d85b584 100644 --- a/packages/bsky/src/index.ts +++ b/packages/bsky/src/index.ts @@ -5,53 +5,36 @@ import events from 'events' import { createHttpTerminator, HttpTerminator } from 'http-terminator' import cors from 'cors' import compression from 'compression' +import AtpAgent from '@atproto/api' import { IdResolver } from '@atproto/identity' -import { - RateLimiter, - RateLimiterOpts, - Options as XrpcServerOptions, -} from '@atproto/xrpc-server' -import { MINUTE } from '@atproto/common' import API, { health, wellKnown, blobResolver } from './api' -import { DatabaseCoordinator } from './db' import * as error from './error' -import { dbLogger, loggerMiddleware } from './logger' +import { loggerMiddleware } from './logger' import { ServerConfig } from './config' import { createServer } from './lexicon' import { ImageUriBuilder } from './image/uri' import { BlobDiskCache, ImageProcessingServer } from './image/server' -import { createServices } from './services' import AppContext from './context' -import DidRedisCache from './did-cache' -import { - ImageInvalidator, - ImageProcessingServerInvalidator, -} from './image/invalidator' -import { BackgroundQueue } from './background' -import { AtpAgent } from '@atproto/api' import { Keypair } from '@atproto/crypto' -import { Redis } from './redis' +import { createDataPlaneClient } from './data-plane/client' +import { Hydrator } from './hydration/hydrator' +import { Views } from './views' import { AuthVerifier } from './auth-verifier' import { authWithApiKey as bsyncAuth, createBsyncClient } from './bsync' import { authWithApiKey as courierAuth, createCourierClient } from './courier' +export * from './data-plane' export type { ServerConfigValues } from './config' export { ServerConfig } from './config' -export { Database, PrimaryDatabase, DatabaseCoordinator } from './db' +export { Database } from './data-plane/server/db' export { Redis } from './redis' -export { ViewMaintainer } from './db/views' export { AppContext } from './context' -export type { ImageInvalidator } from './image/invalidator' -export * from './daemon' -export * from './indexer' -export * from './ingester' export class BskyAppView { public ctx: AppContext public app: express.Application public server?: http.Server private terminator?: HttpTerminator - private dbStatsInterval: NodeJS.Timer constructor(opts: { ctx: AppContext; app: express.Application }) { this.ctx = opts.ctx @@ -59,151 +42,89 @@ export class BskyAppView { } static create(opts: { - db: DatabaseCoordinator - redis: Redis config: ServerConfig signingKey: Keypair - imgInvalidator?: ImageInvalidator }): BskyAppView { - const { db, redis, config, signingKey } = opts - let maybeImgInvalidator = opts.imgInvalidator + const { config, signingKey } = opts const app = express() - app.set('trust proxy', true) app.use(cors()) app.use(loggerMiddleware) app.use(compression()) - const didCache = new DidRedisCache(redis.withNamespace('did-doc'), { - staleTTL: config.didCacheStaleTTL, - maxTTL: config.didCacheMaxTTL, - }) - + // used solely for handle resolution: identity lookups occur on dataplane const idResolver = new IdResolver({ plcUrl: config.didPlcUrl, - didCache, backupNameservers: config.handleResolveNameservers, }) const imgUriBuilder = new ImageUriBuilder( - config.imgUriEndpoint || `${config.publicUrl}/img`, + config.cdnUrl || `${config.publicUrl}/img`, ) let imgProcessingServer: ImageProcessingServer | undefined - if (!config.imgUriEndpoint) { + if (!config.cdnUrl) { const imgProcessingCache = new BlobDiskCache(config.blobCacheLocation) imgProcessingServer = new ImageProcessingServer( config, imgProcessingCache, ) - maybeImgInvalidator ??= new ImageProcessingServerInvalidator( - imgProcessingCache, - ) - } - - let imgInvalidator: ImageInvalidator - if (maybeImgInvalidator) { - imgInvalidator = maybeImgInvalidator - } else { - throw new Error('Missing appview image invalidator') } - const backgroundQueue = new BackgroundQueue(db.getPrimary()) - - const searchAgent = config.searchEndpoint - ? new AtpAgent({ service: config.searchEndpoint }) + const searchAgent = config.searchUrl + ? new AtpAgent({ service: config.searchUrl }) : undefined + const dataplane = createDataPlaneClient(config.dataplaneUrls, { + httpVersion: config.dataplaneHttpVersion, + rejectUnauthorized: !config.dataplaneIgnoreBadTls, + }) + const hydrator = new Hydrator(dataplane, { + labelsFromIssuerDids: config.labelsFromIssuerDids, + }) + const views = new Views(imgUriBuilder) - const services = createServices({ - imgUriBuilder, - imgInvalidator, - labelCacheOpts: { - redis: redis.withNamespace('label'), - staleTTL: config.labelCacheStaleTTL, - maxTTL: config.labelCacheMaxTTL, - }, + const bsyncClient = createBsyncClient({ + baseUrl: config.bsyncUrl, + httpVersion: config.bsyncHttpVersion ?? '2', + nodeOptions: { rejectUnauthorized: !config.bsyncIgnoreBadTls }, + interceptors: config.bsyncApiKey ? [bsyncAuth(config.bsyncApiKey)] : [], + }) + + const courierClient = createCourierClient({ + baseUrl: config.courierUrl, + httpVersion: config.courierHttpVersion ?? '2', + nodeOptions: { rejectUnauthorized: !config.courierIgnoreBadTls }, + interceptors: config.courierApiKey + ? [courierAuth(config.courierApiKey)] + : [], }) - const authVerifier = new AuthVerifier(idResolver, { + const authVerifier = new AuthVerifier(dataplane, { ownDid: config.serverDid, adminDid: config.modServiceDid, - adminPass: config.adminPassword, - moderatorPass: config.moderatorPassword, - triagePass: config.triagePassword, + adminPasses: config.adminPasswords, }) - const bsyncClient = config.bsyncUrl - ? createBsyncClient({ - baseUrl: config.bsyncUrl, - httpVersion: config.bsyncHttpVersion ?? '2', - nodeOptions: { rejectUnauthorized: !config.bsyncIgnoreBadTls }, - interceptors: config.bsyncApiKey - ? [bsyncAuth(config.bsyncApiKey)] - : [], - }) - : undefined - - const courierClient = config.courierUrl - ? createCourierClient({ - baseUrl: config.courierUrl, - httpVersion: config.courierHttpVersion ?? '2', - nodeOptions: { rejectUnauthorized: !config.courierIgnoreBadTls }, - interceptors: config.courierApiKey - ? [courierAuth(config.courierApiKey)] - : [], - }) - : undefined - const ctx = new AppContext({ - db, cfg: config, - services, - imgUriBuilder, + dataplane, + searchAgent, + hydrator, + views, signingKey, idResolver, - didCache, - redis, - backgroundQueue, - searchAgent, bsyncClient, courierClient, authVerifier, }) - const xrpcOpts: XrpcServerOptions = { + let server = createServer({ validateResponse: config.debugMode, payload: { jsonLimit: 100 * 1024, // 100kb textLimit: 100 * 1024, // 100kb blobLimit: 5 * 1024 * 1024, // 5mb }, - } - if (config.rateLimitsEnabled) { - const rlCreator = (opts: RateLimiterOpts) => - RateLimiter.redis(redis.driver, { - bypassSecret: config.rateLimitBypassKey, - bypassIps: config.rateLimitBypassIps, - ...opts, - }) - xrpcOpts['rateLimits'] = { - creator: rlCreator, - global: [ - { - name: 'global-unauthed-ip', - durationMs: 5 * MINUTE, - points: 3000, - calcKey: (ctx) => (ctx.auth ? null : ctx.req.ip), - }, - { - name: 'global-authed-did', - durationMs: 5 * MINUTE, - points: 3000, - calcKey: (ctx) => ctx.auth?.credentials?.did ?? null, - }, - ], - } - } - - let server = createServer(xrpcOpts) + }) server = API(server, ctx) @@ -220,38 +141,6 @@ export class BskyAppView { } async start(): Promise { - const { db, backgroundQueue } = this.ctx - const primary = db.getPrimary() - const replicas = db.getReplicas() - this.dbStatsInterval = setInterval(() => { - dbLogger.info( - { - idleCount: replicas.reduce( - (tot, replica) => tot + replica.pool.idleCount, - 0, - ), - totalCount: replicas.reduce( - (tot, replica) => tot + replica.pool.totalCount, - 0, - ), - waitingCount: replicas.reduce( - (tot, replica) => tot + replica.pool.waitingCount, - 0, - ), - primaryIdleCount: primary.pool.idleCount, - primaryTotalCount: primary.pool.totalCount, - primaryWaitingCount: primary.pool.waitingCount, - }, - 'db pool stats', - ) - dbLogger.info( - { - runningCount: backgroundQueue.queue.pending, - waitingCount: backgroundQueue.queue.size, - }, - 'background queue stats', - ) - }, 10000) const server = this.app.listen(this.ctx.cfg.port) this.server = server server.keepAliveTimeout = 90000 @@ -262,13 +151,8 @@ export class BskyAppView { return server } - async destroy(opts?: { skipDb: boolean; skipRedis: boolean }): Promise { - await this.ctx.didCache.destroy() + async destroy(): Promise { await this.terminator?.terminate() - await this.ctx.backgroundQueue.destroy() - if (!opts?.skipRedis) await this.ctx.redis.destroy() - if (!opts?.skipDb) await this.ctx.db.close() - clearInterval(this.dbStatsInterval) } } diff --git a/packages/bsky/src/indexer/config.ts b/packages/bsky/src/indexer/config.ts deleted file mode 100644 index 1a4c14ff85e..00000000000 --- a/packages/bsky/src/indexer/config.ts +++ /dev/null @@ -1,271 +0,0 @@ -import assert from 'assert' -import { DAY, HOUR, parseIntWithFallback } from '@atproto/common' - -export interface IndexerConfigValues { - version: string - serverDid: string - dbPostgresUrl: string - dbPostgresSchema?: string - redisHost?: string // either set redis host, or both sentinel name and hosts - redisSentinelName?: string - redisSentinelHosts?: string[] - redisPassword?: string - didPlcUrl: string - didCacheStaleTTL: number - didCacheMaxTTL: number - handleResolveNameservers?: string[] - hiveApiKey?: string - imgUriEndpoint?: string - labelerKeywords: Record - moderationPushUrl: string - courierUrl?: string - courierApiKey?: string - courierHttpVersion?: '1.1' | '2' - courierIgnoreBadTls?: boolean - indexerConcurrency?: number - indexerPartitionIds: number[] - indexerPartitionBatchSize?: number - indexerSubLockId?: number - indexerPort?: number - ingesterPartitionCount: number - indexerNamespace?: string - pushNotificationEndpoint?: string -} - -export class IndexerConfig { - constructor(private cfg: IndexerConfigValues) {} - - static readEnv(overrides?: Partial) { - const version = process.env.BSKY_VERSION || '0.0.0' - const serverDid = process.env.SERVER_DID || 'did:example:test' - const dbPostgresUrl = - overrides?.dbPostgresUrl || process.env.DB_PRIMARY_POSTGRES_URL - const dbPostgresSchema = - overrides?.dbPostgresSchema || process.env.DB_POSTGRES_SCHEMA - const redisHost = - overrides?.redisHost || process.env.REDIS_HOST || undefined - const redisSentinelName = - overrides?.redisSentinelName || - process.env.REDIS_SENTINEL_NAME || - undefined - const redisSentinelHosts = - overrides?.redisSentinelHosts || - (process.env.REDIS_SENTINEL_HOSTS - ? process.env.REDIS_SENTINEL_HOSTS.split(',') - : []) - const redisPassword = - overrides?.redisPassword || process.env.REDIS_PASSWORD || undefined - const didPlcUrl = process.env.DID_PLC_URL || 'http://localhost:2582' - const didCacheStaleTTL = parseIntWithFallback( - process.env.DID_CACHE_STALE_TTL, - HOUR, - ) - const didCacheMaxTTL = parseIntWithFallback( - process.env.DID_CACHE_MAX_TTL, - DAY, - ) - const handleResolveNameservers = process.env.HANDLE_RESOLVE_NAMESERVERS - ? process.env.HANDLE_RESOLVE_NAMESERVERS.split(',') - : [] - const moderationPushUrl = - overrides?.moderationPushUrl || - process.env.MODERATION_PUSH_URL || - undefined - assert(moderationPushUrl) - const courierUrl = - overrides?.courierUrl || process.env.BSKY_COURIER_URL || undefined - const courierApiKey = - overrides?.courierApiKey || process.env.BSKY_COURIER_API_KEY || undefined - const courierHttpVersion = - overrides?.courierHttpVersion || - process.env.BSKY_COURIER_HTTP_VERSION || - '2' - const courierIgnoreBadTls = - overrides?.courierIgnoreBadTls || - process.env.BSKY_COURIER_IGNORE_BAD_TLS === 'true' - assert(courierHttpVersion === '1.1' || courierHttpVersion === '2') - const hiveApiKey = process.env.HIVE_API_KEY || undefined - const imgUriEndpoint = process.env.IMG_URI_ENDPOINT - const indexerPartitionIds = - overrides?.indexerPartitionIds || - (process.env.INDEXER_PARTITION_IDS - ? process.env.INDEXER_PARTITION_IDS.split(',').map((n) => - parseInt(n, 10), - ) - : []) - const indexerPartitionBatchSize = maybeParseInt( - process.env.INDEXER_PARTITION_BATCH_SIZE, - ) - const indexerConcurrency = maybeParseInt(process.env.INDEXER_CONCURRENCY) - const indexerNamespace = overrides?.indexerNamespace - const indexerSubLockId = maybeParseInt(process.env.INDEXER_SUB_LOCK_ID) - const indexerPort = maybeParseInt(process.env.INDEXER_PORT) - const ingesterPartitionCount = - maybeParseInt(process.env.INGESTER_PARTITION_COUNT) ?? 64 - const labelerKeywords = {} - const pushNotificationEndpoint = process.env.PUSH_NOTIFICATION_ENDPOINT - assert(dbPostgresUrl) - assert(redisHost || (redisSentinelName && redisSentinelHosts?.length)) - assert(indexerPartitionIds.length > 0) - return new IndexerConfig({ - version, - serverDid, - dbPostgresUrl, - dbPostgresSchema, - redisHost, - redisSentinelName, - redisSentinelHosts, - redisPassword, - didPlcUrl, - didCacheStaleTTL, - didCacheMaxTTL, - handleResolveNameservers, - moderationPushUrl, - courierUrl, - courierApiKey, - courierHttpVersion, - courierIgnoreBadTls, - hiveApiKey, - imgUriEndpoint, - indexerPartitionIds, - indexerConcurrency, - indexerPartitionBatchSize, - indexerNamespace, - indexerSubLockId, - indexerPort, - ingesterPartitionCount, - labelerKeywords, - pushNotificationEndpoint, - ...stripUndefineds(overrides ?? {}), - }) - } - - get version() { - return this.cfg.version - } - - get serverDid() { - return this.cfg.serverDid - } - - get dbPostgresUrl() { - return this.cfg.dbPostgresUrl - } - - get dbPostgresSchema() { - return this.cfg.dbPostgresSchema - } - - get redisHost() { - return this.cfg.redisHost - } - - get redisSentinelName() { - return this.cfg.redisSentinelName - } - - get redisSentinelHosts() { - return this.cfg.redisSentinelHosts - } - - get redisPassword() { - return this.cfg.redisPassword - } - - get didPlcUrl() { - return this.cfg.didPlcUrl - } - - get didCacheStaleTTL() { - return this.cfg.didCacheStaleTTL - } - - get didCacheMaxTTL() { - return this.cfg.didCacheMaxTTL - } - - get handleResolveNameservers() { - return this.cfg.handleResolveNameservers - } - - get moderationPushUrl() { - return this.cfg.moderationPushUrl - } - - get courierUrl() { - return this.cfg.courierUrl - } - - get courierApiKey() { - return this.cfg.courierApiKey - } - - get courierHttpVersion() { - return this.cfg.courierHttpVersion - } - - get courierIgnoreBadTls() { - return this.cfg.courierIgnoreBadTls - } - - get hiveApiKey() { - return this.cfg.hiveApiKey - } - - get imgUriEndpoint() { - return this.cfg.imgUriEndpoint - } - - get indexerConcurrency() { - return this.cfg.indexerConcurrency - } - - get indexerPartitionIds() { - return this.cfg.indexerPartitionIds - } - - get indexerPartitionBatchSize() { - return this.cfg.indexerPartitionBatchSize - } - - get indexerNamespace() { - return this.cfg.indexerNamespace - } - - get indexerSubLockId() { - return this.cfg.indexerSubLockId - } - - get indexerPort() { - return this.cfg.indexerPort - } - - get ingesterPartitionCount() { - return this.cfg.ingesterPartitionCount - } - - get labelerKeywords() { - return this.cfg.labelerKeywords - } - - get pushNotificationEndpoint() { - return this.cfg.pushNotificationEndpoint - } -} - -function stripUndefineds( - obj: Record, -): Record { - const result = {} - Object.entries(obj).forEach(([key, val]) => { - if (val !== undefined) { - result[key] = val - } - }) - return result -} - -function maybeParseInt(str) { - const parsed = parseInt(str) - return isNaN(parsed) ? undefined : parsed -} diff --git a/packages/bsky/src/indexer/context.ts b/packages/bsky/src/indexer/context.ts deleted file mode 100644 index a4c1f1f2ea0..00000000000 --- a/packages/bsky/src/indexer/context.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { IdResolver } from '@atproto/identity' -import { PrimaryDatabase } from '../db' -import { IndexerConfig } from './config' -import { Services } from './services' -import { BackgroundQueue } from '../background' -import DidSqlCache from '../did-cache' -import { Redis } from '../redis' -import { AutoModerator } from '../auto-moderator' -import { NotificationServer } from '../notifications' - -export class IndexerContext { - constructor( - private opts: { - db: PrimaryDatabase - redis: Redis - redisCache: Redis - cfg: IndexerConfig - services: Services - idResolver: IdResolver - didCache: DidSqlCache - backgroundQueue: BackgroundQueue - autoMod: AutoModerator - notifServer?: NotificationServer - }, - ) {} - - get db(): PrimaryDatabase { - return this.opts.db - } - - get redis(): Redis { - return this.opts.redis - } - - get redisCache(): Redis { - return this.opts.redisCache - } - - get cfg(): IndexerConfig { - return this.opts.cfg - } - - get services(): Services { - return this.opts.services - } - - get idResolver(): IdResolver { - return this.opts.idResolver - } - - get didCache(): DidSqlCache { - return this.opts.didCache - } - - get backgroundQueue(): BackgroundQueue { - return this.opts.backgroundQueue - } - - get autoMod(): AutoModerator { - return this.opts.autoMod - } - - get notifServer(): NotificationServer | undefined { - return this.opts.notifServer - } -} - -export default IndexerContext diff --git a/packages/bsky/src/indexer/index.ts b/packages/bsky/src/indexer/index.ts deleted file mode 100644 index 7d012304573..00000000000 --- a/packages/bsky/src/indexer/index.ts +++ /dev/null @@ -1,166 +0,0 @@ -import express from 'express' -import { IdResolver } from '@atproto/identity' -import { BackgroundQueue } from '../background' -import { PrimaryDatabase } from '../db' -import DidRedisCache from '../did-cache' -import log from './logger' -import { dbLogger } from '../logger' -import { IndexerConfig } from './config' -import { IndexerContext } from './context' -import { createServices } from './services' -import { IndexerSubscription } from './subscription' -import { AutoModerator } from '../auto-moderator' -import { Redis } from '../redis' -import { - CourierNotificationServer, - GorushNotificationServer, - NotificationServer, -} from '../notifications' -import { CloseFn, createServer, startServer } from './server' -import { authWithApiKey as courierAuth, createCourierClient } from '../courier' - -export { IndexerConfig } from './config' -export type { IndexerConfigValues } from './config' - -export class BskyIndexer { - public ctx: IndexerContext - public sub: IndexerSubscription - public app: express.Application - private closeServer?: CloseFn - private dbStatsInterval: NodeJS.Timer - private subStatsInterval: NodeJS.Timer - - constructor(opts: { - ctx: IndexerContext - sub: IndexerSubscription - app: express.Application - }) { - this.ctx = opts.ctx - this.sub = opts.sub - this.app = opts.app - } - - static create(opts: { - db: PrimaryDatabase - redis: Redis - redisCache: Redis - cfg: IndexerConfig - }): BskyIndexer { - const { db, redis, redisCache, cfg } = opts - const didCache = new DidRedisCache(redisCache.withNamespace('did-doc'), { - staleTTL: cfg.didCacheStaleTTL, - maxTTL: cfg.didCacheMaxTTL, - }) - const idResolver = new IdResolver({ - plcUrl: cfg.didPlcUrl, - didCache, - backupNameservers: cfg.handleResolveNameservers, - }) - const backgroundQueue = new BackgroundQueue(db) - - const autoMod = new AutoModerator({ - db, - idResolver, - cfg, - backgroundQueue, - }) - - const courierClient = cfg.courierUrl - ? createCourierClient({ - baseUrl: cfg.courierUrl, - httpVersion: cfg.courierHttpVersion ?? '2', - nodeOptions: { rejectUnauthorized: !cfg.courierIgnoreBadTls }, - interceptors: cfg.courierApiKey - ? [courierAuth(cfg.courierApiKey)] - : [], - }) - : undefined - - let notifServer: NotificationServer | undefined - if (courierClient) { - notifServer = new CourierNotificationServer(db, courierClient) - } else if (cfg.pushNotificationEndpoint) { - notifServer = new GorushNotificationServer( - db, - cfg.pushNotificationEndpoint, - ) - } - - const services = createServices({ - idResolver, - autoMod, - backgroundQueue, - notifServer, - }) - const ctx = new IndexerContext({ - db, - redis, - redisCache, - cfg, - services, - idResolver, - didCache, - backgroundQueue, - autoMod, - notifServer, - }) - const sub = new IndexerSubscription(ctx, { - partitionIds: cfg.indexerPartitionIds, - partitionBatchSize: cfg.indexerPartitionBatchSize, - concurrency: cfg.indexerConcurrency, - subLockId: cfg.indexerSubLockId, - }) - - const app = createServer(sub, cfg) - - return new BskyIndexer({ ctx, sub, app }) - } - - async start() { - const { db, backgroundQueue } = this.ctx - const pool = db.pool - this.dbStatsInterval = setInterval(() => { - dbLogger.info( - { - idleCount: pool.idleCount, - totalCount: pool.totalCount, - waitingCount: pool.waitingCount, - }, - 'db pool stats', - ) - dbLogger.info( - { - runningCount: backgroundQueue.queue.pending, - waitingCount: backgroundQueue.queue.size, - }, - 'background queue stats', - ) - }, 10000) - this.subStatsInterval = setInterval(() => { - log.info( - { - processedCount: this.sub.processedCount, - runningCount: this.sub.repoQueue.main.pending, - waitingCount: this.sub.repoQueue.main.size, - }, - 'indexer stats', - ) - }, 500) - this.sub.run() - this.closeServer = startServer(this.app, this.ctx.cfg.indexerPort) - return this - } - - async destroy(opts?: { skipDb: boolean; skipRedis: true }): Promise { - if (this.closeServer) await this.closeServer() - await this.sub.destroy() - clearInterval(this.subStatsInterval) - await this.ctx.didCache.destroy() - if (!opts?.skipRedis) await this.ctx.redis.destroy() - if (!opts?.skipRedis) await this.ctx.redisCache.destroy() - if (!opts?.skipDb) await this.ctx.db.close() - clearInterval(this.dbStatsInterval) - } -} - -export default BskyIndexer diff --git a/packages/bsky/src/indexer/logger.ts b/packages/bsky/src/indexer/logger.ts deleted file mode 100644 index 45752727f99..00000000000 --- a/packages/bsky/src/indexer/logger.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { subsystemLogger } from '@atproto/common' - -const logger: ReturnType = - subsystemLogger('bsky:indexer') - -export default logger diff --git a/packages/bsky/src/indexer/server.ts b/packages/bsky/src/indexer/server.ts deleted file mode 100644 index dfafb741eb4..00000000000 --- a/packages/bsky/src/indexer/server.ts +++ /dev/null @@ -1,46 +0,0 @@ -import express from 'express' -import { IndexerSubscription } from './subscription' -import { IndexerConfig } from './config' -import { randomIntFromSeed } from '@atproto/crypto' - -export type CloseFn = () => Promise - -export const createServer = ( - sub: IndexerSubscription, - cfg: IndexerConfig, -): express.Application => { - const app = express() - app.post('/reprocess/:did', async (req, res) => { - const did = req.params.did - try { - const partition = await randomIntFromSeed(did, cfg.ingesterPartitionCount) - const supportedPartition = cfg.indexerPartitionIds.includes(partition) - if (!supportedPartition) { - return res.status(400).send(`unsupported partition: ${partition}`) - } - } catch (err) { - return res.status(500).send('could not calculate partition') - } - await sub.requestReprocess(req.params.did) - res.sendStatus(200) - }) - return app -} - -export const startServer = ( - app: express.Application, - port?: number, -): CloseFn => { - const server = app.listen(port) - return () => { - return new Promise((resolve, reject) => { - server.close((err) => { - if (err) { - reject(err) - } else { - resolve() - } - }) - }) - } -} diff --git a/packages/bsky/src/indexer/services.ts b/packages/bsky/src/indexer/services.ts deleted file mode 100644 index df173352046..00000000000 --- a/packages/bsky/src/indexer/services.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { IdResolver } from '@atproto/identity' -import { PrimaryDatabase } from '../db' -import { BackgroundQueue } from '../background' -import { IndexingService } from '../services/indexing' -import { LabelService } from '../services/label' -import { NotificationServer } from '../notifications' -import { AutoModerator } from '../auto-moderator' - -export function createServices(resources: { - idResolver: IdResolver - autoMod: AutoModerator - backgroundQueue: BackgroundQueue - notifServer?: NotificationServer -}): Services { - const { idResolver, autoMod, backgroundQueue, notifServer } = resources - return { - indexing: IndexingService.creator( - idResolver, - autoMod, - backgroundQueue, - notifServer, - ), - label: LabelService.creator(null), - } -} - -export type Services = { - indexing: FromDbPrimary - label: FromDbPrimary -} - -type FromDbPrimary = (db: PrimaryDatabase) => T diff --git a/packages/bsky/src/ingester/config.ts b/packages/bsky/src/ingester/config.ts deleted file mode 100644 index 0a3d9e79e5a..00000000000 --- a/packages/bsky/src/ingester/config.ts +++ /dev/null @@ -1,182 +0,0 @@ -import assert from 'assert' - -export interface IngesterConfigValues { - version: string - dbPostgresUrl: string - dbPostgresSchema?: string - redisHost?: string // either set redis host, or both sentinel name and hosts - redisSentinelName?: string - redisSentinelHosts?: string[] - redisPassword?: string - repoProvider: string - labelProvider?: string - bsyncUrl?: string - bsyncApiKey?: string - bsyncHttpVersion?: '1.1' | '2' - bsyncIgnoreBadTls?: boolean - ingesterPartitionCount: number - ingesterNamespace?: string - ingesterSubLockId?: number - ingesterMaxItems?: number - ingesterCheckItemsEveryN?: number - ingesterInitialCursor?: number -} - -export class IngesterConfig { - constructor(private cfg: IngesterConfigValues) {} - - static readEnv(overrides?: Partial) { - const version = process.env.BSKY_VERSION || '0.0.0' - const dbPostgresUrl = - overrides?.dbPostgresUrl || process.env.DB_PRIMARY_POSTGRES_URL - const dbPostgresSchema = - overrides?.dbPostgresSchema || process.env.DB_POSTGRES_SCHEMA - const redisHost = - overrides?.redisHost || process.env.REDIS_HOST || undefined - const redisSentinelName = - overrides?.redisSentinelName || - process.env.REDIS_SENTINEL_NAME || - undefined - const redisSentinelHosts = - overrides?.redisSentinelHosts || - (process.env.REDIS_SENTINEL_HOSTS - ? process.env.REDIS_SENTINEL_HOSTS.split(',') - : []) - const redisPassword = - overrides?.redisPassword || process.env.REDIS_PASSWORD || undefined - const repoProvider = overrides?.repoProvider || process.env.REPO_PROVIDER // E.g. ws://abc.com:4000 - const labelProvider = overrides?.labelProvider || process.env.LABEL_PROVIDER - const bsyncUrl = - overrides?.bsyncUrl || process.env.BSKY_BSYNC_URL || undefined - const bsyncApiKey = - overrides?.bsyncApiKey || process.env.BSKY_BSYNC_API_KEY || undefined - const bsyncHttpVersion = - overrides?.bsyncHttpVersion || process.env.BSKY_BSYNC_HTTP_VERSION || '2' - const bsyncIgnoreBadTls = - overrides?.bsyncIgnoreBadTls || - process.env.BSKY_BSYNC_IGNORE_BAD_TLS === 'true' - assert(bsyncHttpVersion === '1.1' || bsyncHttpVersion === '2') - const ingesterPartitionCount = - overrides?.ingesterPartitionCount || - maybeParseInt(process.env.INGESTER_PARTITION_COUNT) - const ingesterSubLockId = - overrides?.ingesterSubLockId || - maybeParseInt(process.env.INGESTER_SUB_LOCK_ID) - const ingesterMaxItems = - overrides?.ingesterMaxItems || - maybeParseInt(process.env.INGESTER_MAX_ITEMS) - const ingesterCheckItemsEveryN = - overrides?.ingesterCheckItemsEveryN || - maybeParseInt(process.env.INGESTER_CHECK_ITEMS_EVERY_N) - const ingesterInitialCursor = - overrides?.ingesterInitialCursor || - maybeParseInt(process.env.INGESTER_INITIAL_CURSOR) - const ingesterNamespace = overrides?.ingesterNamespace - assert(dbPostgresUrl) - assert(redisHost || (redisSentinelName && redisSentinelHosts?.length)) - assert(repoProvider) - assert(ingesterPartitionCount) - return new IngesterConfig({ - version, - dbPostgresUrl, - dbPostgresSchema, - redisHost, - redisSentinelName, - redisSentinelHosts, - redisPassword, - repoProvider, - labelProvider, - bsyncUrl, - bsyncApiKey, - bsyncHttpVersion, - bsyncIgnoreBadTls, - ingesterPartitionCount, - ingesterSubLockId, - ingesterNamespace, - ingesterMaxItems, - ingesterCheckItemsEveryN, - ingesterInitialCursor, - }) - } - - get version() { - return this.cfg.version - } - - get dbPostgresUrl() { - return this.cfg.dbPostgresUrl - } - - get dbPostgresSchema() { - return this.cfg.dbPostgresSchema - } - - get redisHost() { - return this.cfg.redisHost - } - - get redisSentinelName() { - return this.cfg.redisSentinelName - } - - get redisSentinelHosts() { - return this.cfg.redisSentinelHosts - } - - get redisPassword() { - return this.cfg.redisPassword - } - - get repoProvider() { - return this.cfg.repoProvider - } - - get labelProvider() { - return this.cfg.labelProvider - } - - get bsyncUrl() { - return this.cfg.bsyncUrl - } - - get bsyncApiKey() { - return this.cfg.bsyncApiKey - } - - get bsyncHttpVersion() { - return this.cfg.bsyncHttpVersion - } - - get bsyncIgnoreBadTls() { - return this.cfg.bsyncIgnoreBadTls - } - - get ingesterPartitionCount() { - return this.cfg.ingesterPartitionCount - } - - get ingesterMaxItems() { - return this.cfg.ingesterMaxItems - } - - get ingesterCheckItemsEveryN() { - return this.cfg.ingesterCheckItemsEveryN - } - - get ingesterInitialCursor() { - return this.cfg.ingesterInitialCursor - } - - get ingesterNamespace() { - return this.cfg.ingesterNamespace - } - - get ingesterSubLockId() { - return this.cfg.ingesterSubLockId - } -} - -function maybeParseInt(str) { - const parsed = parseInt(str) - return isNaN(parsed) ? undefined : parsed -} diff --git a/packages/bsky/src/ingester/context.ts b/packages/bsky/src/ingester/context.ts deleted file mode 100644 index debf9843ea6..00000000000 --- a/packages/bsky/src/ingester/context.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { PrimaryDatabase } from '../db' -import { Redis } from '../redis' -import { IngesterConfig } from './config' -import { LabelSubscription } from './label-subscription' -import { MuteSubscription } from './mute-subscription' - -export class IngesterContext { - constructor( - private opts: { - db: PrimaryDatabase - redis: Redis - cfg: IngesterConfig - labelSubscription?: LabelSubscription - muteSubscription?: MuteSubscription - }, - ) {} - - get db(): PrimaryDatabase { - return this.opts.db - } - - get redis(): Redis { - return this.opts.redis - } - - get cfg(): IngesterConfig { - return this.opts.cfg - } - - get labelSubscription(): LabelSubscription | undefined { - return this.opts.labelSubscription - } - - get muteSubscription(): MuteSubscription | undefined { - return this.opts.muteSubscription - } -} - -export default IngesterContext diff --git a/packages/bsky/src/ingester/index.ts b/packages/bsky/src/ingester/index.ts deleted file mode 100644 index 76225f13d38..00000000000 --- a/packages/bsky/src/ingester/index.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { PrimaryDatabase } from '../db' -import log from './logger' -import { dbLogger } from '../logger' -import { Redis } from '../redis' -import { IngesterConfig } from './config' -import { IngesterContext } from './context' -import { IngesterSubscription } from './subscription' -import { authWithApiKey, createBsyncClient } from '../bsync' -import { LabelSubscription } from './label-subscription' -import { MuteSubscription } from './mute-subscription' - -export { IngesterConfig } from './config' -export type { IngesterConfigValues } from './config' - -export class BskyIngester { - public ctx: IngesterContext - public sub: IngesterSubscription - private dbStatsInterval: NodeJS.Timer - private subStatsInterval: NodeJS.Timer - - constructor(opts: { ctx: IngesterContext; sub: IngesterSubscription }) { - this.ctx = opts.ctx - this.sub = opts.sub - } - - static create(opts: { - db: PrimaryDatabase - redis: Redis - cfg: IngesterConfig - }): BskyIngester { - const { db, redis, cfg } = opts - const bsyncClient = cfg.bsyncUrl - ? createBsyncClient({ - baseUrl: cfg.bsyncUrl, - httpVersion: cfg.bsyncHttpVersion ?? '2', - nodeOptions: { rejectUnauthorized: !cfg.bsyncIgnoreBadTls }, - interceptors: cfg.bsyncApiKey - ? [authWithApiKey(cfg.bsyncApiKey)] - : [], - }) - : undefined - const labelSubscription = cfg.labelProvider - ? new LabelSubscription(db, cfg.labelProvider) - : undefined - const muteSubscription = bsyncClient - ? new MuteSubscription(db, redis, bsyncClient) - : undefined - const ctx = new IngesterContext({ - db, - redis, - cfg, - labelSubscription, - muteSubscription, - }) - const sub = new IngesterSubscription(ctx, { - service: cfg.repoProvider, - subLockId: cfg.ingesterSubLockId, - partitionCount: cfg.ingesterPartitionCount, - maxItems: cfg.ingesterMaxItems, - checkItemsEveryN: cfg.ingesterCheckItemsEveryN, - initialCursor: cfg.ingesterInitialCursor, - }) - return new BskyIngester({ ctx, sub }) - } - - async start() { - const { db } = this.ctx - const pool = db.pool - this.dbStatsInterval = setInterval(() => { - dbLogger.info( - { - idleCount: pool.idleCount, - totalCount: pool.totalCount, - waitingCount: pool.waitingCount, - }, - 'db pool stats', - ) - }, 10000) - this.subStatsInterval = setInterval(() => { - log.info( - { - seq: this.sub.lastSeq, - streamsLength: - this.sub.backpressure.lastTotal !== null - ? this.sub.backpressure.lastTotal - : undefined, - }, - 'ingester stats', - ) - }, 500) - await this.ctx.labelSubscription?.start() - await this.ctx.muteSubscription?.start() - this.sub.run() - return this - } - - async destroy(opts?: { skipDb: boolean }): Promise { - await this.ctx.muteSubscription?.destroy() - await this.ctx.labelSubscription?.destroy() - await this.sub.destroy() - clearInterval(this.subStatsInterval) - await this.ctx.redis.destroy() - if (!opts?.skipDb) await this.ctx.db.close() - clearInterval(this.dbStatsInterval) - } -} - -export default BskyIngester diff --git a/packages/bsky/src/ingester/label-subscription.ts b/packages/bsky/src/ingester/label-subscription.ts deleted file mode 100644 index d486473cf98..00000000000 --- a/packages/bsky/src/ingester/label-subscription.ts +++ /dev/null @@ -1,76 +0,0 @@ -import AtpAgent from '@atproto/api' -import { PrimaryDatabase } from '../db' -import { sql } from 'kysely' -import { dbLogger } from '../logger' -import { SECOND } from '@atproto/common' - -export class LabelSubscription { - destroyed = false - promise: Promise = Promise.resolve() - timer: NodeJS.Timer | undefined - lastLabel: number | undefined - labelAgent: AtpAgent - - constructor(public db: PrimaryDatabase, public labelProvider: string) { - this.labelAgent = new AtpAgent({ service: labelProvider }) - } - - async start() { - const res = await this.db.db - .selectFrom('label') - .select('cts') - .orderBy('cts', 'desc') - .limit(1) - .executeTakeFirst() - this.lastLabel = res ? new Date(res.cts).getTime() : undefined - this.poll() - } - - poll() { - if (this.destroyed) return - this.promise = this.fetchLabels() - .catch((err) => - dbLogger.error({ err }, 'failed to fetch and store labels'), - ) - .finally(() => { - this.timer = setTimeout(() => this.poll(), SECOND) - }) - } - - async fetchLabels() { - const res = await this.labelAgent.api.com.atproto.temp.fetchLabels({ - since: this.lastLabel, - }) - const last = res.data.labels.at(-1) - if (!last) { - return - } - const dbVals = res.data.labels.map((l) => ({ - ...l, - cid: l.cid ?? '', - neg: l.neg ?? false, - })) - const { ref } = this.db.db.dynamic - const excluded = (col: string) => ref(`excluded.${col}`) - await this.db - .asPrimary() - .db.insertInto('label') - .values(dbVals) - .onConflict((oc) => - oc.columns(['src', 'uri', 'cid', 'val']).doUpdateSet({ - neg: sql`${excluded('neg')}`, - cts: sql`${excluded('cts')}`, - }), - ) - .execute() - this.lastLabel = new Date(last.cts).getTime() - } - - async destroy() { - this.destroyed = true - if (this.timer) { - clearTimeout(this.timer) - } - await this.promise - } -} diff --git a/packages/bsky/src/ingester/logger.ts b/packages/bsky/src/ingester/logger.ts deleted file mode 100644 index 49855166481..00000000000 --- a/packages/bsky/src/ingester/logger.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { subsystemLogger } from '@atproto/common' - -const logger: ReturnType = - subsystemLogger('bsky:ingester') - -export default logger diff --git a/packages/bsky/src/ingester/mute-subscription.ts b/packages/bsky/src/ingester/mute-subscription.ts deleted file mode 100644 index 9adb685c160..00000000000 --- a/packages/bsky/src/ingester/mute-subscription.ts +++ /dev/null @@ -1,213 +0,0 @@ -import assert from 'node:assert' -import { PrimaryDatabase } from '../db' -import { Redis } from '../redis' -import { BsyncClient, Code, isBsyncError } from '../bsync' -import { MuteOperation, MuteOperation_Type } from '../proto/bsync_pb' -import logger from './logger' -import { wait } from '@atproto/common' -import { - AtUri, - InvalidDidError, - ensureValidAtUri, - ensureValidDid, -} from '@atproto/syntax' -import { ids } from '../lexicon/lexicons' - -const CURSOR_KEY = 'ingester:mute:cursor' - -export class MuteSubscription { - ac = new AbortController() - running: Promise | undefined - cursor: string | null = null - - constructor( - public db: PrimaryDatabase, - public redis: Redis, - public bsyncClient: BsyncClient, - ) {} - - async start() { - if (this.running) return - this.ac = new AbortController() - this.running = this.run() - .catch((err) => { - // allow this to cause an unhandled rejection, let deployment handle the crash. - logger.error({ err }, 'mute subscription crashed') - throw err - }) - .finally(() => (this.running = undefined)) - } - - private async run() { - this.cursor = await this.getCursor() - while (!this.ac.signal.aborted) { - try { - // get page of mute ops, long-polling - const page = await this.bsyncClient.scanMuteOperations( - { - limit: 100, - cursor: this.cursor ?? undefined, - }, - { signal: this.ac.signal }, - ) - if (!page.cursor) { - throw new BadResponseError('cursor is missing') - } - // process - const now = new Date() - for (const op of page.operations) { - if (this.ac.signal.aborted) return - if (op.type === MuteOperation_Type.ADD) { - await this.handleAddOp(op, now) - } else if (op.type === MuteOperation_Type.REMOVE) { - await this.handleRemoveOp(op) - } else if (op.type === MuteOperation_Type.CLEAR) { - await this.handleClearOp(op) - } else { - logger.warn( - { id: op.id, type: op.type }, - 'unknown mute subscription op type', - ) - } - } - // update cursor - await this.setCursor(page.cursor) - this.cursor = page.cursor - } catch (err) { - if (isBsyncError(err, Code.Canceled)) { - return // canceled, probably from destroy() - } - if (err instanceof BadResponseError) { - logger.warn({ err }, 'bad response from bsync') - } else { - logger.error({ err }, 'unexpected error processing mute subscription') - } - await wait(1000) // wait a second before trying again - } - } - } - - async handleAddOp(op: MuteOperation, createdAt: Date) { - assert(op.type === MuteOperation_Type.ADD) - if (!isValidDid(op.actorDid)) { - logger.warn({ id: op.id, type: op.type }, 'bad actor in mute op') - return - } - if (isValidDid(op.subject)) { - await this.db.db - .insertInto('mute') - .values({ - subjectDid: op.subject, - mutedByDid: op.actorDid, - createdAt: createdAt.toISOString(), - }) - .onConflict((oc) => oc.doNothing()) - .execute() - } else { - const listUri = isValidAtUri(op.subject) - ? new AtUri(op.subject) - : undefined - if (listUri?.collection !== ids.AppBskyGraphList) { - logger.warn({ id: op.id, type: op.type }, 'bad subject in mute op') - return - } - await this.db.db - .insertInto('list_mute') - .values({ - listUri: op.subject, - mutedByDid: op.actorDid, - createdAt: createdAt.toISOString(), - }) - .onConflict((oc) => oc.doNothing()) - .execute() - } - } - - async handleRemoveOp(op: MuteOperation) { - assert(op.type === MuteOperation_Type.REMOVE) - if (!isValidDid(op.actorDid)) { - logger.warn({ id: op.id, type: op.type }, 'bad actor in mute op') - return - } - if (isValidDid(op.subject)) { - await this.db.db - .deleteFrom('mute') - .where('subjectDid', '=', op.subject) - .where('mutedByDid', '=', op.actorDid) - .execute() - } else { - const listUri = isValidAtUri(op.subject) - ? new AtUri(op.subject) - : undefined - if (listUri?.collection !== ids.AppBskyGraphList) { - logger.warn({ id: op.id, type: op.type }, 'bad subject in mute op') - return - } - await this.db.db - .deleteFrom('list_mute') - .where('listUri', '=', op.subject) - .where('mutedByDid', '=', op.actorDid) - .execute() - } - } - - async handleClearOp(op: MuteOperation) { - assert(op.type === MuteOperation_Type.CLEAR) - if (!isValidDid(op.actorDid)) { - logger.warn({ id: op.id, type: op.type }, 'bad actor in mute op') - return - } - if (op.subject) { - logger.warn({ id: op.id, type: op.type }, 'bad subject in mute op') - return - } - await this.db.db - .deleteFrom('mute') - .where('mutedByDid', '=', op.actorDid) - .execute() - await this.db.db - .deleteFrom('list_mute') - .where('mutedByDid', '=', op.actorDid) - .execute() - } - - async getCursor(): Promise { - return await this.redis.get(CURSOR_KEY) - } - - async setCursor(cursor: string): Promise { - await this.redis.set(CURSOR_KEY, cursor) - } - - async destroy() { - this.ac.abort() - await this.running - } - - get destroyed() { - return this.ac.signal.aborted - } -} - -class BadResponseError extends Error {} - -const isValidDid = (did: string) => { - try { - ensureValidDid(did) - return true - } catch (err) { - if (err instanceof InvalidDidError) { - return false - } - throw err - } -} - -const isValidAtUri = (uri: string) => { - try { - ensureValidAtUri(uri) - return true - } catch { - return false - } -} diff --git a/packages/bsky/src/ingester/subscription.ts b/packages/bsky/src/ingester/subscription.ts deleted file mode 100644 index 6db05ba1786..00000000000 --- a/packages/bsky/src/ingester/subscription.ts +++ /dev/null @@ -1,290 +0,0 @@ -import { - Deferrable, - cborEncode, - createDeferrable, - ui8ToBuffer, - wait, -} from '@atproto/common' -import { randomIntFromSeed } from '@atproto/crypto' -import { DisconnectError, Subscription } from '@atproto/xrpc-server' -import { OutputSchema as Message } from '../lexicon/types/com/atproto/sync/subscribeRepos' -import * as message from '../lexicon/types/com/atproto/sync/subscribeRepos' -import { ids, lexicons } from '../lexicon/lexicons' -import { Leader } from '../db/leader' -import log from './logger' -import { - LatestQueue, - ProcessableMessage, - loggableMessage, - jitter, - strToInt, -} from '../subscription/util' -import { IngesterContext } from './context' - -const METHOD = ids.ComAtprotoSyncSubscribeRepos -const CURSOR_KEY = 'ingester:cursor' -export const INGESTER_SUB_LOCK_ID = 1000 - -export class IngesterSubscription { - cursorQueue = new LatestQueue() - destroyed = false - lastSeq: number | undefined - backpressure = new Backpressure(this) - leader = new Leader(this.opts.subLockId || INGESTER_SUB_LOCK_ID, this.ctx.db) - processor = new Processor(this) - - constructor( - public ctx: IngesterContext, - public opts: { - service: string - partitionCount: number - maxItems?: number - checkItemsEveryN?: number - subLockId?: number - initialCursor?: number - }, - ) {} - - async run() { - while (!this.destroyed) { - try { - const { ran } = await this.leader.run(async ({ signal }) => { - const sub = this.getSubscription({ signal }) - for await (const msg of sub) { - const details = getMessageDetails(msg) - if ('info' in details) { - // These messages are not sequenced, we just log them and carry on - log.warn( - { provider: this.opts.service, message: loggableMessage(msg) }, - `ingester sub ${details.info ? 'info' : 'unknown'} message`, - ) - continue - } - this.processor.send(details) - await this.backpressure.ready() - } - }) - if (ran && !this.destroyed) { - throw new Error('Ingester sub completed, but should be persistent') - } - } catch (err) { - log.error({ err, provider: this.opts.service }, 'ingester sub error') - } - if (!this.destroyed) { - await wait(1000 + jitter(500)) // wait then try to become leader - } - } - } - - async destroy() { - this.destroyed = true - await this.processor.destroy() - await this.cursorQueue.destroy() - this.leader.destroy(new DisconnectError()) - } - - async resume() { - this.destroyed = false - this.processor = new Processor(this) - this.cursorQueue = new LatestQueue() - await this.run() - } - - async getCursor(): Promise { - const val = await this.ctx.redis.get(CURSOR_KEY) - const initialCursor = this.opts.initialCursor ?? 0 - return val !== null ? strToInt(val) : initialCursor - } - - async resetCursor(): Promise { - await this.ctx.redis.del(CURSOR_KEY) - } - - async setCursor(seq: number): Promise { - await this.ctx.redis.set(CURSOR_KEY, seq) - } - - private getSubscription(opts: { signal: AbortSignal }) { - return new Subscription({ - service: this.opts.service, - method: METHOD, - signal: opts.signal, - getParams: async () => { - const cursor = await this.getCursor() - return { cursor } - }, - onReconnectError: (err, reconnects, initial) => { - log.warn({ err, reconnects, initial }, 'ingester sub reconnect') - }, - validate: (value) => { - try { - return lexicons.assertValidXrpcMessage(METHOD, value) - } catch (err) { - log.warn( - { - err, - seq: ifNumber(value?.['seq']), - repo: ifString(value?.['repo']), - commit: ifString(value?.['commit']?.toString()), - time: ifString(value?.['time']), - provider: this.opts.service, - }, - 'ingester sub skipped invalid message', - ) - } - }, - }) - } -} - -function ifString(val: unknown): string | undefined { - return typeof val === 'string' ? val : undefined -} - -function ifNumber(val: unknown): number | undefined { - return typeof val === 'number' ? val : undefined -} - -function getMessageDetails(msg: Message): - | { info: message.Info | null } - | { - seq: number - repo: string - message: ProcessableMessage - } { - if (message.isCommit(msg)) { - return { seq: msg.seq, repo: msg.repo, message: msg } - } else if (message.isHandle(msg)) { - return { seq: msg.seq, repo: msg.did, message: msg } - } else if (message.isIdentity(msg)) { - return { seq: msg.seq, repo: msg.did, message: msg } - } else if (message.isMigrate(msg)) { - return { seq: msg.seq, repo: msg.did, message: msg } - } else if (message.isTombstone(msg)) { - return { seq: msg.seq, repo: msg.did, message: msg } - } else if (message.isInfo(msg)) { - return { info: msg } - } - return { info: null } -} - -async function getPartition(did: string, n: number) { - const partition = await randomIntFromSeed(did, n) - return `repo:${partition}` -} - -class Processor { - running: Deferrable | null = null - destroyed = false - unprocessed: MessageEnvelope[] = [] - - constructor(public sub: IngesterSubscription) {} - - async handleBatch(batch: MessageEnvelope[]) { - if (!batch.length) return - const items = await Promise.all( - batch.map(async ({ seq, repo, message }) => { - const key = await getPartition(repo, this.sub.opts.partitionCount) - const fields: [string, string | Buffer][] = [ - ['repo', repo], - ['event', ui8ToBuffer(cborEncode(message))], - ] - return { key, id: seq, fields } - }), - ) - const results = await this.sub.ctx.redis.addMultiToStream(items) - results.forEach(([err], i) => { - if (err) { - // skipping over messages that have already been added or fully processed - const item = batch.at(i) - log.warn( - { seq: item?.seq, repo: item?.repo }, - 'ingester skipping message', - ) - } - }) - const lastSeq = batch[batch.length - 1].seq - this.sub.lastSeq = lastSeq - this.sub.cursorQueue.add(() => this.sub.setCursor(lastSeq)) - } - - async process() { - if (this.running || this.destroyed || !this.unprocessed.length) return - const next = this.unprocessed.splice(100) // pipeline no more than 100 - const processing = this.unprocessed - this.unprocessed = next - this.running = createDeferrable() - try { - await this.handleBatch(processing) - } catch (err) { - log.error( - { err, size: processing.length }, - 'ingester processing failed, rolling over to next batch', - ) - this.unprocessed.unshift(...processing) - } finally { - this.running.resolve() - this.running = null - this.process() - } - } - - send(envelope: MessageEnvelope) { - this.unprocessed.push(envelope) - this.process() - } - - async destroy() { - this.destroyed = true - this.unprocessed = [] - await this.running?.complete - } -} - -type MessageEnvelope = { - seq: number - repo: string - message: ProcessableMessage -} - -class Backpressure { - count = 0 - lastTotal: number | null = null - partitionCount = this.sub.opts.partitionCount - limit = this.sub.opts.maxItems ?? Infinity - checkEvery = this.sub.opts.checkItemsEveryN ?? 500 - - constructor(public sub: IngesterSubscription) {} - - async ready() { - this.count++ - const shouldCheck = - this.limit !== Infinity && - (this.count === 1 || this.count % this.checkEvery === 0) - if (!shouldCheck) return - let ready = false - const start = Date.now() - while (!ready) { - ready = await this.check() - if (!ready) { - log.warn( - { - limit: this.limit, - total: this.lastTotal, - duration: Date.now() - start, - }, - 'ingester backpressure', - ) - await wait(250) - } - } - } - - async check() { - const lens = await this.sub.ctx.redis.streamLengths( - [...Array(this.partitionCount)].map((_, i) => `repo:${i}`), - ) - this.lastTotal = lens.reduce((sum, len) => sum + len, 0) - return this.lastTotal < this.limit - } -} diff --git a/packages/bsky/src/notifications.ts b/packages/bsky/src/notifications.ts deleted file mode 100644 index d29913ec7d4..00000000000 --- a/packages/bsky/src/notifications.ts +++ /dev/null @@ -1,398 +0,0 @@ -import axios from 'axios' -import { Insertable, sql } from 'kysely' -import TTLCache from '@isaacs/ttlcache' -import { Struct, Timestamp } from '@bufbuild/protobuf' -import murmur from 'murmurhash' -import { AtUri } from '@atproto/api' -import { MINUTE, chunkArray } from '@atproto/common' -import Database from './db/primary' -import { Notification } from './db/tables/notification' -import { NotificationPushToken as PushToken } from './db/tables/notification-push-token' -import logger from './indexer/logger' -import { notSoftDeletedClause, valuesList } from './db/util' -import { ids } from './lexicon/lexicons' -import { retryConnect, retryHttp } from './util/retry' -import { Notification as CourierNotification } from './proto/courier_pb' -import { CourierClient } from './courier' - -export type Platform = 'ios' | 'android' | 'web' - -type GorushNotification = { - tokens: string[] - platform: 1 | 2 // 1 = ios, 2 = android - title: string - message: string - topic: string - data?: { - [key: string]: string - } - collapse_id?: string - collapse_key?: string -} - -type NotifRow = Insertable - -type NotifView = { - key: string - rateLimit: boolean - title: string - body: string - notif: NotifRow -} - -export abstract class NotificationServer { - constructor(public db: Database) {} - - abstract prepareNotifications(notifs: NotifRow[]): Promise - - abstract processNotifications(prepared: N[]): Promise - - async getNotificationViews(notifs: NotifRow[]): Promise { - const { ref } = this.db.db.dynamic - const authorDids = notifs.map((n) => n.author) - const subjectUris = notifs.flatMap((n) => n.reasonSubject ?? []) - const recordUris = notifs.map((n) => n.recordUri) - const allUris = [...subjectUris, ...recordUris] - - // gather potential display data for notifications in batch - const [authors, posts, blocksAndMutes] = await Promise.all([ - this.db.db - .selectFrom('actor') - .leftJoin('profile', 'profile.creator', 'actor.did') - .leftJoin('record', 'record.uri', 'profile.uri') - .where(notSoftDeletedClause(ref('actor'))) - .where(notSoftDeletedClause(ref('record'))) - .where('profile.creator', 'in', authorDids.length ? authorDids : ['']) - .select(['actor.did as did', 'handle', 'displayName']) - .execute(), - this.db.db - .selectFrom('post') - .innerJoin('actor', 'actor.did', 'post.creator') - .innerJoin('record', 'record.uri', 'post.uri') - .where(notSoftDeletedClause(ref('actor'))) - .where(notSoftDeletedClause(ref('record'))) - .where('post.uri', 'in', allUris.length ? allUris : ['']) - .select(['post.uri as uri', 'text']) - .execute(), - this.findBlocksAndMutes(notifs), - ]) - - const authorsByDid = authors.reduce((acc, author) => { - acc[author.did] = author - return acc - }, {} as Record) - const postsByUri = posts.reduce((acc, post) => { - acc[post.uri] = post - return acc - }, {} as Record) - - const results: NotifView[] = [] - - for (const notif of notifs) { - const { - author: authorDid, - reason, - reasonSubject: subjectUri, // if like/reply/quote/mention, the post which was liked/replied to/mention is in/or quoted. if custom feed liked, the feed which was liked - recordUri, - } = notif - - const author = - authorsByDid[authorDid]?.displayName || authorsByDid[authorDid]?.handle - const postRecord = postsByUri[recordUri] - const postSubject = subjectUri ? postsByUri[subjectUri] : null - - // if blocked or muted, don't send notification - const shouldFilter = blocksAndMutes.some( - (pair) => pair.author === notif.author && pair.receiver === notif.did, - ) - if (shouldFilter || !author) { - // if no display name, dont send notification - continue - } - // const author = displayName.displayName - - // 2. Get post data content - // if follow, get the URI of the author's profile - // if reply, or mention, get URI of the postRecord - // if like, or custom feed like, or repost get the URI of the reasonSubject - const key = reason - let title = '' - let body = '' - let rateLimit = true - - // check follow first and mention first because they don't have subjectUri and return - // reply has subjectUri but the recordUri is the replied post - if (reason === 'follow') { - title = 'New follower!' - body = `${author} has followed you` - results.push({ key, title, body, notif, rateLimit }) - continue - } else if (reason === 'mention' || reason === 'reply') { - // use recordUri for mention and reply - title = - reason === 'mention' - ? `${author} mentioned you` - : `${author} replied to your post` - body = postRecord?.text || '' - rateLimit = false // always deliver - results.push({ key, title, body, notif, rateLimit }) - continue - } - - // if no subjectUri, don't send notification - // at this point, subjectUri should exist for all the other reasons - if (!postSubject) { - continue - } - - if (reason === 'like') { - title = `${author} liked your post` - body = postSubject?.text || '' - // custom feed like - const uri = subjectUri ? new AtUri(subjectUri) : null - if (uri?.collection === ids.AppBskyFeedGenerator) { - title = `${author} liked your custom feed` - body = uri?.rkey ?? '' - } - } else if (reason === 'quote') { - title = `${author} quoted your post` - body = postSubject?.text || '' - rateLimit = true // always deliver - } else if (reason === 'repost') { - title = `${author} reposted your post` - body = postSubject?.text || '' - } - - if (title === '' && body === '') { - logger.warn( - { notif }, - 'No notification display attributes found for this notification. Either profile or post data for this notification is missing.', - ) - continue - } - - results.push({ key, title, body, notif, rateLimit }) - } - - return results - } - - private async findBlocksAndMutes(notifs: NotifRow[]) { - const pairs = notifs.map((n) => ({ author: n.author, receiver: n.did })) - const { ref } = this.db.db.dynamic - const blockQb = this.db.db - .selectFrom('actor_block') - .where((outer) => - outer - .where((qb) => - qb - .whereRef('actor_block.creator', '=', ref('author')) - .whereRef('actor_block.subjectDid', '=', ref('receiver')), - ) - .orWhere((qb) => - qb - .whereRef('actor_block.creator', '=', ref('receiver')) - .whereRef('actor_block.subjectDid', '=', ref('author')), - ), - ) - .select(['creator', 'subjectDid']) - const muteQb = this.db.db - .selectFrom('mute') - .whereRef('mute.subjectDid', '=', ref('author')) - .whereRef('mute.mutedByDid', '=', ref('receiver')) - .selectAll() - const muteListQb = this.db.db - .selectFrom('list_item') - .innerJoin('list_mute', 'list_mute.listUri', 'list_item.listUri') - .whereRef('list_mute.mutedByDid', '=', ref('receiver')) - .whereRef('list_item.subjectDid', '=', ref('author')) - .select('list_item.subjectDid') - - const values = valuesList(pairs.map((p) => sql`${p.author}, ${p.receiver}`)) - const filterPairs = await this.db.db - .selectFrom(values.as(sql`pair (author, receiver)`)) - .whereExists(muteQb) - .orWhereExists(muteListQb) - .orWhereExists(blockQb) - .selectAll() - .execute() - return filterPairs as { author: string; receiver: string }[] - } -} - -export class GorushNotificationServer extends NotificationServer { - private rateLimiter = new RateLimiter(1, 30 * MINUTE) - - constructor(public db: Database, public pushEndpoint: string) { - super(db) - } - - async prepareNotifications( - notifs: NotifRow[], - ): Promise { - const now = Date.now() - const notifsToSend: GorushNotification[] = [] - const tokensByDid = await this.getTokensByDid( - unique(notifs.map((n) => n.did)), - ) - // views for all notifications that have tokens - const notificationViews = await this.getNotificationViews( - notifs.filter((n) => tokensByDid[n.did]), - ) - - for (const notifView of notificationViews) { - if (!isRecent(notifView.notif.sortAt, 10 * MINUTE)) { - continue // if the notif is from > 10 minutes ago, don't send push notif - } - const { did: userDid } = notifView.notif - const userTokens = tokensByDid[userDid] ?? [] - for (const t of userTokens) { - const { appId, platform, token } = t - if (notifView.rateLimit && !this.rateLimiter.check(token, now)) { - continue - } - if (platform === 'ios' || platform === 'android') { - notifsToSend.push({ - tokens: [token], - platform: platform === 'ios' ? 1 : 2, - title: notifView.title, - message: notifView.body, - topic: appId, - data: { - reason: notifView.notif.reason, - recordUri: notifView.notif.recordUri, - recordCid: notifView.notif.recordCid, - }, - collapse_id: notifView.key, - collapse_key: notifView.key, - }) - } else { - // @TODO: Handle web notifs - logger.warn({ did: userDid }, 'cannot send web notification to user') - } - } - } - return notifsToSend - } - - async getTokensByDid(dids: string[]) { - if (!dids.length) return {} - const tokens = await this.db.db - .selectFrom('notification_push_token') - .where('did', 'in', dids) - .selectAll() - .execute() - return tokens.reduce((acc, token) => { - acc[token.did] ??= [] - acc[token.did].push(token) - return acc - }, {} as Record) - } - - async processNotifications(prepared: GorushNotification[]): Promise { - for (const batch of chunkArray(prepared, 20)) { - try { - await this.sendToGorush(batch) - } catch (err) { - logger.error({ err, batch }, 'notification push batch failed') - } - } - } - - private async sendToGorush(prepared: GorushNotification[]) { - // if no notifications, skip and return early - if (prepared.length === 0) { - return - } - const pushEndpoint = this.pushEndpoint - await retryHttp(() => - axios.post( - pushEndpoint, - { notifications: prepared }, - { - headers: { - 'content-type': 'application/json', - accept: 'application/json', - }, - }, - ), - ) - } -} - -export class CourierNotificationServer extends NotificationServer { - constructor(public db: Database, public courierClient: CourierClient) { - super(db) - } - - async prepareNotifications( - notifs: NotifRow[], - ): Promise { - const notificationViews = await this.getNotificationViews(notifs) - const notifsToSend = notificationViews.map((n) => { - return new CourierNotification({ - id: getCourierId(n), - recipientDid: n.notif.did, - title: n.title, - message: n.body, - collapseKey: n.key, - alwaysDeliver: !n.rateLimit, - timestamp: Timestamp.fromDate(new Date(n.notif.sortAt)), - additional: Struct.fromJson({ - uri: n.notif.recordUri, - reason: n.notif.reason, - subject: n.notif.reasonSubject || '', - }), - }) - }) - return notifsToSend - } - - async processNotifications(prepared: CourierNotification[]): Promise { - try { - await retryConnect(() => - this.courierClient.pushNotifications({ notifications: prepared }), - ) - } catch (err) { - logger.error({ err }, 'notification push to courier failed') - } - } -} - -const getCourierId = (notif: NotifView) => { - const key = [ - notif.notif.recordUri, - notif.notif.did, - notif.notif.reason, - notif.notif.reasonSubject || '', - ].join('::') - return murmur.v3(key).toString(16) -} - -const isRecent = (isoTime: string, timeDiff: number): boolean => { - const diff = Date.now() - new Date(isoTime).getTime() - return diff < timeDiff -} - -const unique = (items: string[]) => [...new Set(items)] - -class RateLimiter { - private rateLimitCache = new TTLCache({ - max: 50000, - ttl: this.windowMs, - noUpdateTTL: true, - }) - constructor(private limit: number, private windowMs: number) {} - check(token: string, now = Date.now()) { - const key = getRateLimitKey(token, now) - const last = this.rateLimitCache.get(key) ?? 0 - const current = last + 1 - this.rateLimitCache.set(key, current) - return current <= this.limit - } -} - -const getRateLimitKey = (token: string, now: number) => { - const iteration = Math.floor(now / (20 * MINUTE)) - return `${iteration}:${token}` -} diff --git a/packages/bsky/src/pipeline.ts b/packages/bsky/src/pipeline.ts index 7798519bfa2..50f1abfd566 100644 --- a/packages/bsky/src/pipeline.ts +++ b/packages/bsky/src/pipeline.ts @@ -1,22 +1,48 @@ -export function createPipeline< - Params, - SkeletonState, - HydrationState extends SkeletonState, - View, - Context, ->( - skeleton: (params: Params, ctx: Context) => Promise, - hydration: (state: SkeletonState, ctx: Context) => Promise, - rules: (state: HydrationState, ctx: Context) => HydrationState, - presentation: (state: HydrationState, ctx: Context) => View, +import { HydrationState } from './hydration/hydrator' + +export function createPipeline( + skeletonFn: (input: SkeletonFnInput) => Promise, + hydrationFn: ( + input: HydrationFnInput, + ) => Promise, + rulesFn: (input: RulesFnInput) => Skeleton, + presentationFn: ( + input: PresentationFnInput, + ) => View, ) { return async (params: Params, ctx: Context) => { - const skeletonState = await skeleton(params, ctx) - const hydrationState = await hydration(skeletonState, ctx) - return presentation(rules(hydrationState, ctx), ctx) + const skeleton = await skeletonFn({ ctx, params }) + const hydration = await hydrationFn({ ctx, params, skeleton }) + const appliedRules = rulesFn({ ctx, params, skeleton, hydration }) + return presentationFn({ ctx, params, skeleton: appliedRules, hydration }) } } -export function noRules(state: T) { - return state +export type SkeletonFnInput = { + ctx: Context + params: Params +} + +export type HydrationFnInput = { + ctx: Context + params: Params + skeleton: Skeleton +} + +export type RulesFnInput = { + ctx: Context + params: Params + skeleton: Skeleton + hydration: HydrationState +} + +export type PresentationFnInput = { + ctx: Context + params: Params + skeleton: Skeleton + hydration: HydrationState +} + +export function noRules(input: { skeleton: S }) { + return input.skeleton } diff --git a/packages/bsky/src/proto/bsky_connect.ts b/packages/bsky/src/proto/bsky_connect.ts new file mode 100644 index 00000000000..00e2e5b9204 --- /dev/null +++ b/packages/bsky/src/proto/bsky_connect.ts @@ -0,0 +1,920 @@ +// @generated by protoc-gen-connect-es v1.3.0 with parameter "target=ts,import_extension=.ts" +// @generated from file bsky.proto (package bsky, syntax proto3) +/* eslint-disable */ +// @ts-nocheck + +import { + ClearActorMutelistSubscriptionsRequest, + ClearActorMutelistSubscriptionsResponse, + ClearActorMutesRequest, + ClearActorMutesResponse, + CreateActorMutelistSubscriptionRequest, + CreateActorMutelistSubscriptionResponse, + CreateActorMuteRequest, + CreateActorMuteResponse, + DeleteActorMutelistSubscriptionRequest, + DeleteActorMutelistSubscriptionResponse, + DeleteActorMuteRequest, + DeleteActorMuteResponse, + GetActorFeedsRequest, + GetActorFeedsResponse, + GetActorFollowsActorsRequest, + GetActorFollowsActorsResponse, + GetActorLikesRequest, + GetActorLikesResponse, + GetActorListsRequest, + GetActorListsResponse, + GetActorMutesActorRequest, + GetActorMutesActorResponse, + GetActorMutesActorViaListRequest, + GetActorMutesActorViaListResponse, + GetActorRepostsRequest, + GetActorRepostsResponse, + GetActorsRequest, + GetActorsResponse, + GetActorTakedownRequest, + GetActorTakedownResponse, + GetAuthorFeedRequest, + GetAuthorFeedResponse, + GetBidirectionalBlockRequest, + GetBidirectionalBlockResponse, + GetBidirectionalBlockViaListRequest, + GetBidirectionalBlockViaListResponse, + GetBlobTakedownRequest, + GetBlobTakedownResponse, + GetBlockExistenceRequest, + GetBlockExistenceResponse, + GetBlocklistSubscriptionRequest, + GetBlocklistSubscriptionResponse, + GetBlocklistSubscriptionsRequest, + GetBlocklistSubscriptionsResponse, + GetBlockRecordsRequest, + GetBlockRecordsResponse, + GetBlocksRequest, + GetBlocksResponse, + GetCountsForUsersRequest, + GetCountsForUsersResponse, + GetDidsByHandlesRequest, + GetDidsByHandlesResponse, + GetFeedGeneratorRecordsRequest, + GetFeedGeneratorRecordsResponse, + GetFeedGeneratorStatusRequest, + GetFeedGeneratorStatusResponse, + GetFollowersRequest, + GetFollowersResponse, + GetFollowRecordsRequest, + GetFollowRecordsResponse, + GetFollowsRequest, + GetFollowsResponse, + GetFollowSuggestionsRequest, + GetFollowSuggestionsResponse, + GetIdentityByDidRequest, + GetIdentityByDidResponse, + GetIdentityByHandleRequest, + GetIdentityByHandleResponse, + GetInteractionCountsRequest, + GetInteractionCountsResponse, + GetLabelsRequest, + GetLabelsResponse, + GetLatestRevRequest, + GetLatestRevResponse, + GetLikeRecordsRequest, + GetLikeRecordsResponse, + GetLikesByActorAndSubjectsRequest, + GetLikesByActorAndSubjectsResponse, + GetLikesBySubjectRequest, + GetLikesBySubjectResponse, + GetListBlockRecordsRequest, + GetListBlockRecordsResponse, + GetListCountRequest, + GetListCountResponse, + GetListFeedRequest, + GetListFeedResponse, + GetListItemRecordsRequest, + GetListItemRecordsResponse, + GetListMembershipRequest, + GetListMembershipResponse, + GetListMembersRequest, + GetListMembersResponse, + GetListRecordsRequest, + GetListRecordsResponse, + GetMutelistSubscriptionRequest, + GetMutelistSubscriptionResponse, + GetMutelistSubscriptionsRequest, + GetMutelistSubscriptionsResponse, + GetMutesRequest, + GetMutesResponse, + GetNotificationSeenRequest, + GetNotificationSeenResponse, + GetNotificationsRequest, + GetNotificationsResponse, + GetPostRecordsRequest, + GetPostRecordsResponse, + GetPostReplyCountsRequest, + GetPostReplyCountsResponse, + GetProfileRecordsRequest, + GetProfileRecordsResponse, + GetRecordTakedownRequest, + GetRecordTakedownResponse, + GetRelationshipsRequest, + GetRelationshipsResponse, + GetRepostRecordsRequest, + GetRepostRecordsResponse, + GetRepostsByActorAndSubjectsRequest, + GetRepostsByActorAndSubjectsResponse, + GetRepostsBySubjectRequest, + GetRepostsBySubjectResponse, + GetSuggestedEntitiesRequest, + GetSuggestedEntitiesResponse, + GetSuggestedFeedsRequest, + GetSuggestedFeedsResponse, + GetThreadGateRecordsRequest, + GetThreadGateRecordsResponse, + GetThreadRequest, + GetThreadResponse, + GetTimelineRequest, + GetTimelineResponse, + GetUnreadNotificationCountRequest, + GetUnreadNotificationCountResponse, + PingRequest, + PingResponse, + SearchActorsRequest, + SearchActorsResponse, + SearchFeedGeneratorsRequest, + SearchFeedGeneratorsResponse, + SearchPostsRequest, + SearchPostsResponse, + TakedownActorRequest, + TakedownActorResponse, + TakedownBlobRequest, + TakedownBlobResponse, + TakedownRecordRequest, + TakedownRecordResponse, + UntakedownActorRequest, + UntakedownActorResponse, + UntakedownBlobRequest, + UntakedownBlobResponse, + UntakedownRecordRequest, + UntakedownRecordResponse, + UpdateNotificationSeenRequest, + UpdateNotificationSeenResponse, +} from './bsky_pb.ts' +import { MethodKind } from '@bufbuild/protobuf' + +/** + * + * Read Path + * + * + * @generated from service bsky.Service + */ +export const Service = { + typeName: 'bsky.Service', + methods: { + /** + * Records + * + * @generated from rpc bsky.Service.GetBlockRecords + */ + getBlockRecords: { + name: 'GetBlockRecords', + I: GetBlockRecordsRequest, + O: GetBlockRecordsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetFeedGeneratorRecords + */ + getFeedGeneratorRecords: { + name: 'GetFeedGeneratorRecords', + I: GetFeedGeneratorRecordsRequest, + O: GetFeedGeneratorRecordsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetFollowRecords + */ + getFollowRecords: { + name: 'GetFollowRecords', + I: GetFollowRecordsRequest, + O: GetFollowRecordsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetLikeRecords + */ + getLikeRecords: { + name: 'GetLikeRecords', + I: GetLikeRecordsRequest, + O: GetLikeRecordsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetListBlockRecords + */ + getListBlockRecords: { + name: 'GetListBlockRecords', + I: GetListBlockRecordsRequest, + O: GetListBlockRecordsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetListItemRecords + */ + getListItemRecords: { + name: 'GetListItemRecords', + I: GetListItemRecordsRequest, + O: GetListItemRecordsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetListRecords + */ + getListRecords: { + name: 'GetListRecords', + I: GetListRecordsRequest, + O: GetListRecordsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetPostRecords + */ + getPostRecords: { + name: 'GetPostRecords', + I: GetPostRecordsRequest, + O: GetPostRecordsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetProfileRecords + */ + getProfileRecords: { + name: 'GetProfileRecords', + I: GetProfileRecordsRequest, + O: GetProfileRecordsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetRepostRecords + */ + getRepostRecords: { + name: 'GetRepostRecords', + I: GetRepostRecordsRequest, + O: GetRepostRecordsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetThreadGateRecords + */ + getThreadGateRecords: { + name: 'GetThreadGateRecords', + I: GetThreadGateRecordsRequest, + O: GetThreadGateRecordsResponse, + kind: MethodKind.Unary, + }, + /** + * Follows + * + * @generated from rpc bsky.Service.GetActorFollowsActors + */ + getActorFollowsActors: { + name: 'GetActorFollowsActors', + I: GetActorFollowsActorsRequest, + O: GetActorFollowsActorsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetFollowers + */ + getFollowers: { + name: 'GetFollowers', + I: GetFollowersRequest, + O: GetFollowersResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetFollows + */ + getFollows: { + name: 'GetFollows', + I: GetFollowsRequest, + O: GetFollowsResponse, + kind: MethodKind.Unary, + }, + /** + * Likes + * + * @generated from rpc bsky.Service.GetLikesBySubject + */ + getLikesBySubject: { + name: 'GetLikesBySubject', + I: GetLikesBySubjectRequest, + O: GetLikesBySubjectResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetLikesByActorAndSubjects + */ + getLikesByActorAndSubjects: { + name: 'GetLikesByActorAndSubjects', + I: GetLikesByActorAndSubjectsRequest, + O: GetLikesByActorAndSubjectsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetActorLikes + */ + getActorLikes: { + name: 'GetActorLikes', + I: GetActorLikesRequest, + O: GetActorLikesResponse, + kind: MethodKind.Unary, + }, + /** + * Reposts + * + * @generated from rpc bsky.Service.GetRepostsBySubject + */ + getRepostsBySubject: { + name: 'GetRepostsBySubject', + I: GetRepostsBySubjectRequest, + O: GetRepostsBySubjectResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetRepostsByActorAndSubjects + */ + getRepostsByActorAndSubjects: { + name: 'GetRepostsByActorAndSubjects', + I: GetRepostsByActorAndSubjectsRequest, + O: GetRepostsByActorAndSubjectsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetActorReposts + */ + getActorReposts: { + name: 'GetActorReposts', + I: GetActorRepostsRequest, + O: GetActorRepostsResponse, + kind: MethodKind.Unary, + }, + /** + * Interaction Counts + * + * @generated from rpc bsky.Service.GetInteractionCounts + */ + getInteractionCounts: { + name: 'GetInteractionCounts', + I: GetInteractionCountsRequest, + O: GetInteractionCountsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetCountsForUsers + */ + getCountsForUsers: { + name: 'GetCountsForUsers', + I: GetCountsForUsersRequest, + O: GetCountsForUsersResponse, + kind: MethodKind.Unary, + }, + /** + * Profile + * + * @generated from rpc bsky.Service.GetActors + */ + getActors: { + name: 'GetActors', + I: GetActorsRequest, + O: GetActorsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetDidsByHandles + */ + getDidsByHandles: { + name: 'GetDidsByHandles', + I: GetDidsByHandlesRequest, + O: GetDidsByHandlesResponse, + kind: MethodKind.Unary, + }, + /** + * Relationships + * + * @generated from rpc bsky.Service.GetRelationships + */ + getRelationships: { + name: 'GetRelationships', + I: GetRelationshipsRequest, + O: GetRelationshipsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetBlockExistence + */ + getBlockExistence: { + name: 'GetBlockExistence', + I: GetBlockExistenceRequest, + O: GetBlockExistenceResponse, + kind: MethodKind.Unary, + }, + /** + * Lists + * + * @generated from rpc bsky.Service.GetActorLists + */ + getActorLists: { + name: 'GetActorLists', + I: GetActorListsRequest, + O: GetActorListsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetListMembers + */ + getListMembers: { + name: 'GetListMembers', + I: GetListMembersRequest, + O: GetListMembersResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetListMembership + */ + getListMembership: { + name: 'GetListMembership', + I: GetListMembershipRequest, + O: GetListMembershipResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetListCount + */ + getListCount: { + name: 'GetListCount', + I: GetListCountRequest, + O: GetListCountResponse, + kind: MethodKind.Unary, + }, + /** + * Mutes + * + * @generated from rpc bsky.Service.GetActorMutesActor + */ + getActorMutesActor: { + name: 'GetActorMutesActor', + I: GetActorMutesActorRequest, + O: GetActorMutesActorResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetMutes + */ + getMutes: { + name: 'GetMutes', + I: GetMutesRequest, + O: GetMutesResponse, + kind: MethodKind.Unary, + }, + /** + * Mutelists + * + * @generated from rpc bsky.Service.GetActorMutesActorViaList + */ + getActorMutesActorViaList: { + name: 'GetActorMutesActorViaList', + I: GetActorMutesActorViaListRequest, + O: GetActorMutesActorViaListResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetMutelistSubscription + */ + getMutelistSubscription: { + name: 'GetMutelistSubscription', + I: GetMutelistSubscriptionRequest, + O: GetMutelistSubscriptionResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetMutelistSubscriptions + */ + getMutelistSubscriptions: { + name: 'GetMutelistSubscriptions', + I: GetMutelistSubscriptionsRequest, + O: GetMutelistSubscriptionsResponse, + kind: MethodKind.Unary, + }, + /** + * Blocks + * + * @generated from rpc bsky.Service.GetBidirectionalBlock + */ + getBidirectionalBlock: { + name: 'GetBidirectionalBlock', + I: GetBidirectionalBlockRequest, + O: GetBidirectionalBlockResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetBlocks + */ + getBlocks: { + name: 'GetBlocks', + I: GetBlocksRequest, + O: GetBlocksResponse, + kind: MethodKind.Unary, + }, + /** + * Blocklists + * + * @generated from rpc bsky.Service.GetBidirectionalBlockViaList + */ + getBidirectionalBlockViaList: { + name: 'GetBidirectionalBlockViaList', + I: GetBidirectionalBlockViaListRequest, + O: GetBidirectionalBlockViaListResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetBlocklistSubscription + */ + getBlocklistSubscription: { + name: 'GetBlocklistSubscription', + I: GetBlocklistSubscriptionRequest, + O: GetBlocklistSubscriptionResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetBlocklistSubscriptions + */ + getBlocklistSubscriptions: { + name: 'GetBlocklistSubscriptions', + I: GetBlocklistSubscriptionsRequest, + O: GetBlocklistSubscriptionsResponse, + kind: MethodKind.Unary, + }, + /** + * Notifications + * + * @generated from rpc bsky.Service.GetNotifications + */ + getNotifications: { + name: 'GetNotifications', + I: GetNotificationsRequest, + O: GetNotificationsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetNotificationSeen + */ + getNotificationSeen: { + name: 'GetNotificationSeen', + I: GetNotificationSeenRequest, + O: GetNotificationSeenResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetUnreadNotificationCount + */ + getUnreadNotificationCount: { + name: 'GetUnreadNotificationCount', + I: GetUnreadNotificationCountRequest, + O: GetUnreadNotificationCountResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.UpdateNotificationSeen + */ + updateNotificationSeen: { + name: 'UpdateNotificationSeen', + I: UpdateNotificationSeenRequest, + O: UpdateNotificationSeenResponse, + kind: MethodKind.Unary, + }, + /** + * FeedGenerators + * + * @generated from rpc bsky.Service.GetActorFeeds + */ + getActorFeeds: { + name: 'GetActorFeeds', + I: GetActorFeedsRequest, + O: GetActorFeedsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetSuggestedFeeds + */ + getSuggestedFeeds: { + name: 'GetSuggestedFeeds', + I: GetSuggestedFeedsRequest, + O: GetSuggestedFeedsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetFeedGeneratorStatus + */ + getFeedGeneratorStatus: { + name: 'GetFeedGeneratorStatus', + I: GetFeedGeneratorStatusRequest, + O: GetFeedGeneratorStatusResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.SearchFeedGenerators + */ + searchFeedGenerators: { + name: 'SearchFeedGenerators', + I: SearchFeedGeneratorsRequest, + O: SearchFeedGeneratorsResponse, + kind: MethodKind.Unary, + }, + /** + * Feeds + * + * @generated from rpc bsky.Service.GetAuthorFeed + */ + getAuthorFeed: { + name: 'GetAuthorFeed', + I: GetAuthorFeedRequest, + O: GetAuthorFeedResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetTimeline + */ + getTimeline: { + name: 'GetTimeline', + I: GetTimelineRequest, + O: GetTimelineResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetListFeed + */ + getListFeed: { + name: 'GetListFeed', + I: GetListFeedRequest, + O: GetListFeedResponse, + kind: MethodKind.Unary, + }, + /** + * Threads + * + * @generated from rpc bsky.Service.GetThread + */ + getThread: { + name: 'GetThread', + I: GetThreadRequest, + O: GetThreadResponse, + kind: MethodKind.Unary, + }, + /** + * Search + * + * @generated from rpc bsky.Service.SearchActors + */ + searchActors: { + name: 'SearchActors', + I: SearchActorsRequest, + O: SearchActorsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.SearchPosts + */ + searchPosts: { + name: 'SearchPosts', + I: SearchPostsRequest, + O: SearchPostsResponse, + kind: MethodKind.Unary, + }, + /** + * Suggestions + * + * @generated from rpc bsky.Service.GetFollowSuggestions + */ + getFollowSuggestions: { + name: 'GetFollowSuggestions', + I: GetFollowSuggestionsRequest, + O: GetFollowSuggestionsResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetSuggestedEntities + */ + getSuggestedEntities: { + name: 'GetSuggestedEntities', + I: GetSuggestedEntitiesRequest, + O: GetSuggestedEntitiesResponse, + kind: MethodKind.Unary, + }, + /** + * Posts + * + * @generated from rpc bsky.Service.GetPostReplyCounts + */ + getPostReplyCounts: { + name: 'GetPostReplyCounts', + I: GetPostReplyCountsRequest, + O: GetPostReplyCountsResponse, + kind: MethodKind.Unary, + }, + /** + * Labels + * + * @generated from rpc bsky.Service.GetLabels + */ + getLabels: { + name: 'GetLabels', + I: GetLabelsRequest, + O: GetLabelsResponse, + kind: MethodKind.Unary, + }, + /** + * Sync + * + * @generated from rpc bsky.Service.GetLatestRev + */ + getLatestRev: { + name: 'GetLatestRev', + I: GetLatestRevRequest, + O: GetLatestRevResponse, + kind: MethodKind.Unary, + }, + /** + * Moderation + * + * @generated from rpc bsky.Service.GetBlobTakedown + */ + getBlobTakedown: { + name: 'GetBlobTakedown', + I: GetBlobTakedownRequest, + O: GetBlobTakedownResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetRecordTakedown + */ + getRecordTakedown: { + name: 'GetRecordTakedown', + I: GetRecordTakedownRequest, + O: GetRecordTakedownResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetActorTakedown + */ + getActorTakedown: { + name: 'GetActorTakedown', + I: GetActorTakedownRequest, + O: GetActorTakedownResponse, + kind: MethodKind.Unary, + }, + /** + * Identity + * + * @generated from rpc bsky.Service.GetIdentityByDid + */ + getIdentityByDid: { + name: 'GetIdentityByDid', + I: GetIdentityByDidRequest, + O: GetIdentityByDidResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.GetIdentityByHandle + */ + getIdentityByHandle: { + name: 'GetIdentityByHandle', + I: GetIdentityByHandleRequest, + O: GetIdentityByHandleResponse, + kind: MethodKind.Unary, + }, + /** + * Ping + * + * @generated from rpc bsky.Service.Ping + */ + ping: { + name: 'Ping', + I: PingRequest, + O: PingResponse, + kind: MethodKind.Unary, + }, + /** + * Moderation + * + * @generated from rpc bsky.Service.TakedownBlob + */ + takedownBlob: { + name: 'TakedownBlob', + I: TakedownBlobRequest, + O: TakedownBlobResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.TakedownRecord + */ + takedownRecord: { + name: 'TakedownRecord', + I: TakedownRecordRequest, + O: TakedownRecordResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.TakedownActor + */ + takedownActor: { + name: 'TakedownActor', + I: TakedownActorRequest, + O: TakedownActorResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.UntakedownBlob + */ + untakedownBlob: { + name: 'UntakedownBlob', + I: UntakedownBlobRequest, + O: UntakedownBlobResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.UntakedownRecord + */ + untakedownRecord: { + name: 'UntakedownRecord', + I: UntakedownRecordRequest, + O: UntakedownRecordResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.UntakedownActor + */ + untakedownActor: { + name: 'UntakedownActor', + I: UntakedownActorRequest, + O: UntakedownActorResponse, + kind: MethodKind.Unary, + }, + /** + * Ingestion + * + * @generated from rpc bsky.Service.CreateActorMute + */ + createActorMute: { + name: 'CreateActorMute', + I: CreateActorMuteRequest, + O: CreateActorMuteResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.DeleteActorMute + */ + deleteActorMute: { + name: 'DeleteActorMute', + I: DeleteActorMuteRequest, + O: DeleteActorMuteResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.ClearActorMutes + */ + clearActorMutes: { + name: 'ClearActorMutes', + I: ClearActorMutesRequest, + O: ClearActorMutesResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.CreateActorMutelistSubscription + */ + createActorMutelistSubscription: { + name: 'CreateActorMutelistSubscription', + I: CreateActorMutelistSubscriptionRequest, + O: CreateActorMutelistSubscriptionResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.DeleteActorMutelistSubscription + */ + deleteActorMutelistSubscription: { + name: 'DeleteActorMutelistSubscription', + I: DeleteActorMutelistSubscriptionRequest, + O: DeleteActorMutelistSubscriptionResponse, + kind: MethodKind.Unary, + }, + /** + * @generated from rpc bsky.Service.ClearActorMutelistSubscriptions + */ + clearActorMutelistSubscriptions: { + name: 'ClearActorMutelistSubscriptions', + I: ClearActorMutelistSubscriptionsRequest, + O: ClearActorMutelistSubscriptionsResponse, + kind: MethodKind.Unary, + }, + }, +} as const diff --git a/packages/bsky/src/proto/bsky_pb.ts b/packages/bsky/src/proto/bsky_pb.ts new file mode 100644 index 00000000000..7c5ddcf1865 --- /dev/null +++ b/packages/bsky/src/proto/bsky_pb.ts @@ -0,0 +1,10619 @@ +// @generated by protoc-gen-es v1.6.0 with parameter "target=ts,import_extension=.ts" +// @generated from file bsky.proto (package bsky, syntax proto3) +/* eslint-disable */ +// @ts-nocheck + +import type { + BinaryReadOptions, + FieldList, + JsonReadOptions, + JsonValue, + PartialMessage, + PlainMessage, +} from '@bufbuild/protobuf' +import { Message, proto3, protoInt64, Timestamp } from '@bufbuild/protobuf' + +/** + * @generated from enum bsky.FeedType + */ +export enum FeedType { + /** + * @generated from enum value: FEED_TYPE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: FEED_TYPE_POSTS_AND_AUTHOR_THREADS = 1; + */ + POSTS_AND_AUTHOR_THREADS = 1, + + /** + * @generated from enum value: FEED_TYPE_POSTS_NO_REPLIES = 2; + */ + POSTS_NO_REPLIES = 2, + + /** + * @generated from enum value: FEED_TYPE_POSTS_WITH_MEDIA = 3; + */ + POSTS_WITH_MEDIA = 3, +} +// Retrieve enum metadata with: proto3.getEnumType(FeedType) +proto3.util.setEnumType(FeedType, 'bsky.FeedType', [ + { no: 0, name: 'FEED_TYPE_UNSPECIFIED' }, + { no: 1, name: 'FEED_TYPE_POSTS_AND_AUTHOR_THREADS' }, + { no: 2, name: 'FEED_TYPE_POSTS_NO_REPLIES' }, + { no: 3, name: 'FEED_TYPE_POSTS_WITH_MEDIA' }, +]) + +/** + * @generated from message bsky.Record + */ +export class Record extends Message { + /** + * @generated from field: bytes record = 1; + */ + record = new Uint8Array(0) + + /** + * @generated from field: string cid = 2; + */ + cid = '' + + /** + * @generated from field: google.protobuf.Timestamp indexed_at = 4; + */ + indexedAt?: Timestamp + + /** + * @generated from field: bool taken_down = 5; + */ + takenDown = false + + /** + * @generated from field: google.protobuf.Timestamp created_at = 6; + */ + createdAt?: Timestamp + + /** + * @generated from field: google.protobuf.Timestamp sorted_at = 7; + */ + sortedAt?: Timestamp + + /** + * @generated from field: string takedown_ref = 8; + */ + takedownRef = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.Record' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'record', kind: 'scalar', T: 12 /* ScalarType.BYTES */ }, + { no: 2, name: 'cid', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 4, name: 'indexed_at', kind: 'message', T: Timestamp }, + { no: 5, name: 'taken_down', kind: 'scalar', T: 8 /* ScalarType.BOOL */ }, + { no: 6, name: 'created_at', kind: 'message', T: Timestamp }, + { no: 7, name: 'sorted_at', kind: 'message', T: Timestamp }, + { + no: 8, + name: 'takedown_ref', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): Record { + return new Record().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): Record { + return new Record().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): Record { + return new Record().fromJsonString(jsonString, options) + } + + static equals( + a: Record | PlainMessage | undefined, + b: Record | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(Record, a, b) + } +} + +/** + * @generated from message bsky.GetBlockRecordsRequest + */ +export class GetBlockRecordsRequest extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetBlockRecordsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetBlockRecordsRequest { + return new GetBlockRecordsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetBlockRecordsRequest { + return new GetBlockRecordsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetBlockRecordsRequest { + return new GetBlockRecordsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetBlockRecordsRequest + | PlainMessage + | undefined, + b: + | GetBlockRecordsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetBlockRecordsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetBlockRecordsResponse + */ +export class GetBlockRecordsResponse extends Message { + /** + * @generated from field: repeated bsky.Record records = 1; + */ + records: Record[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetBlockRecordsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'records', kind: 'message', T: Record, repeated: true }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetBlockRecordsResponse { + return new GetBlockRecordsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetBlockRecordsResponse { + return new GetBlockRecordsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetBlockRecordsResponse { + return new GetBlockRecordsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetBlockRecordsResponse + | PlainMessage + | undefined, + b: + | GetBlockRecordsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetBlockRecordsResponse, a, b) + } +} + +/** + * @generated from message bsky.GetFeedGeneratorRecordsRequest + */ +export class GetFeedGeneratorRecordsRequest extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetFeedGeneratorRecordsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetFeedGeneratorRecordsRequest { + return new GetFeedGeneratorRecordsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetFeedGeneratorRecordsRequest { + return new GetFeedGeneratorRecordsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetFeedGeneratorRecordsRequest { + return new GetFeedGeneratorRecordsRequest().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetFeedGeneratorRecordsRequest + | PlainMessage + | undefined, + b: + | GetFeedGeneratorRecordsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetFeedGeneratorRecordsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetFeedGeneratorRecordsResponse + */ +export class GetFeedGeneratorRecordsResponse extends Message { + /** + * @generated from field: repeated bsky.Record records = 1; + */ + records: Record[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetFeedGeneratorRecordsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'records', kind: 'message', T: Record, repeated: true }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetFeedGeneratorRecordsResponse { + return new GetFeedGeneratorRecordsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetFeedGeneratorRecordsResponse { + return new GetFeedGeneratorRecordsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetFeedGeneratorRecordsResponse { + return new GetFeedGeneratorRecordsResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetFeedGeneratorRecordsResponse + | PlainMessage + | undefined, + b: + | GetFeedGeneratorRecordsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetFeedGeneratorRecordsResponse, a, b) + } +} + +/** + * @generated from message bsky.GetFollowRecordsRequest + */ +export class GetFollowRecordsRequest extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetFollowRecordsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetFollowRecordsRequest { + return new GetFollowRecordsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetFollowRecordsRequest { + return new GetFollowRecordsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetFollowRecordsRequest { + return new GetFollowRecordsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetFollowRecordsRequest + | PlainMessage + | undefined, + b: + | GetFollowRecordsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetFollowRecordsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetFollowRecordsResponse + */ +export class GetFollowRecordsResponse extends Message { + /** + * @generated from field: repeated bsky.Record records = 1; + */ + records: Record[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetFollowRecordsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'records', kind: 'message', T: Record, repeated: true }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetFollowRecordsResponse { + return new GetFollowRecordsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetFollowRecordsResponse { + return new GetFollowRecordsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetFollowRecordsResponse { + return new GetFollowRecordsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetFollowRecordsResponse + | PlainMessage + | undefined, + b: + | GetFollowRecordsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetFollowRecordsResponse, a, b) + } +} + +/** + * @generated from message bsky.GetLikeRecordsRequest + */ +export class GetLikeRecordsRequest extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetLikeRecordsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetLikeRecordsRequest { + return new GetLikeRecordsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetLikeRecordsRequest { + return new GetLikeRecordsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetLikeRecordsRequest { + return new GetLikeRecordsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetLikeRecordsRequest | PlainMessage | undefined, + b: GetLikeRecordsRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetLikeRecordsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetLikeRecordsResponse + */ +export class GetLikeRecordsResponse extends Message { + /** + * @generated from field: repeated bsky.Record records = 1; + */ + records: Record[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetLikeRecordsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'records', kind: 'message', T: Record, repeated: true }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetLikeRecordsResponse { + return new GetLikeRecordsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetLikeRecordsResponse { + return new GetLikeRecordsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetLikeRecordsResponse { + return new GetLikeRecordsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetLikeRecordsResponse + | PlainMessage + | undefined, + b: + | GetLikeRecordsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetLikeRecordsResponse, a, b) + } +} + +/** + * @generated from message bsky.GetListBlockRecordsRequest + */ +export class GetListBlockRecordsRequest extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetListBlockRecordsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetListBlockRecordsRequest { + return new GetListBlockRecordsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetListBlockRecordsRequest { + return new GetListBlockRecordsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetListBlockRecordsRequest { + return new GetListBlockRecordsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetListBlockRecordsRequest + | PlainMessage + | undefined, + b: + | GetListBlockRecordsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetListBlockRecordsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetListBlockRecordsResponse + */ +export class GetListBlockRecordsResponse extends Message { + /** + * @generated from field: repeated bsky.Record records = 1; + */ + records: Record[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetListBlockRecordsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'records', kind: 'message', T: Record, repeated: true }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetListBlockRecordsResponse { + return new GetListBlockRecordsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetListBlockRecordsResponse { + return new GetListBlockRecordsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetListBlockRecordsResponse { + return new GetListBlockRecordsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetListBlockRecordsResponse + | PlainMessage + | undefined, + b: + | GetListBlockRecordsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetListBlockRecordsResponse, a, b) + } +} + +/** + * @generated from message bsky.GetListItemRecordsRequest + */ +export class GetListItemRecordsRequest extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetListItemRecordsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetListItemRecordsRequest { + return new GetListItemRecordsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetListItemRecordsRequest { + return new GetListItemRecordsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetListItemRecordsRequest { + return new GetListItemRecordsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetListItemRecordsRequest + | PlainMessage + | undefined, + b: + | GetListItemRecordsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetListItemRecordsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetListItemRecordsResponse + */ +export class GetListItemRecordsResponse extends Message { + /** + * @generated from field: repeated bsky.Record records = 1; + */ + records: Record[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetListItemRecordsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'records', kind: 'message', T: Record, repeated: true }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetListItemRecordsResponse { + return new GetListItemRecordsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetListItemRecordsResponse { + return new GetListItemRecordsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetListItemRecordsResponse { + return new GetListItemRecordsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetListItemRecordsResponse + | PlainMessage + | undefined, + b: + | GetListItemRecordsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetListItemRecordsResponse, a, b) + } +} + +/** + * @generated from message bsky.GetListRecordsRequest + */ +export class GetListRecordsRequest extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetListRecordsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetListRecordsRequest { + return new GetListRecordsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetListRecordsRequest { + return new GetListRecordsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetListRecordsRequest { + return new GetListRecordsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetListRecordsRequest | PlainMessage | undefined, + b: GetListRecordsRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetListRecordsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetListRecordsResponse + */ +export class GetListRecordsResponse extends Message { + /** + * @generated from field: repeated bsky.Record records = 1; + */ + records: Record[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetListRecordsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'records', kind: 'message', T: Record, repeated: true }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetListRecordsResponse { + return new GetListRecordsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetListRecordsResponse { + return new GetListRecordsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetListRecordsResponse { + return new GetListRecordsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetListRecordsResponse + | PlainMessage + | undefined, + b: + | GetListRecordsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetListRecordsResponse, a, b) + } +} + +/** + * @generated from message bsky.PostRecordMeta + */ +export class PostRecordMeta extends Message { + /** + * @generated from field: bool violates_thread_gate = 1; + */ + violatesThreadGate = false + + /** + * @generated from field: bool has_media = 2; + */ + hasMedia = false + + /** + * @generated from field: bool is_reply = 3; + */ + isReply = false + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.PostRecordMeta' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'violates_thread_gate', + kind: 'scalar', + T: 8 /* ScalarType.BOOL */, + }, + { no: 2, name: 'has_media', kind: 'scalar', T: 8 /* ScalarType.BOOL */ }, + { no: 3, name: 'is_reply', kind: 'scalar', T: 8 /* ScalarType.BOOL */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): PostRecordMeta { + return new PostRecordMeta().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): PostRecordMeta { + return new PostRecordMeta().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): PostRecordMeta { + return new PostRecordMeta().fromJsonString(jsonString, options) + } + + static equals( + a: PostRecordMeta | PlainMessage | undefined, + b: PostRecordMeta | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(PostRecordMeta, a, b) + } +} + +/** + * @generated from message bsky.GetPostRecordsRequest + */ +export class GetPostRecordsRequest extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetPostRecordsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetPostRecordsRequest { + return new GetPostRecordsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetPostRecordsRequest { + return new GetPostRecordsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetPostRecordsRequest { + return new GetPostRecordsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetPostRecordsRequest | PlainMessage | undefined, + b: GetPostRecordsRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetPostRecordsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetPostRecordsResponse + */ +export class GetPostRecordsResponse extends Message { + /** + * @generated from field: repeated bsky.Record records = 1; + */ + records: Record[] = [] + + /** + * @generated from field: repeated bsky.PostRecordMeta meta = 2; + */ + meta: PostRecordMeta[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetPostRecordsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'records', kind: 'message', T: Record, repeated: true }, + { no: 2, name: 'meta', kind: 'message', T: PostRecordMeta, repeated: true }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetPostRecordsResponse { + return new GetPostRecordsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetPostRecordsResponse { + return new GetPostRecordsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetPostRecordsResponse { + return new GetPostRecordsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetPostRecordsResponse + | PlainMessage + | undefined, + b: + | GetPostRecordsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetPostRecordsResponse, a, b) + } +} + +/** + * @generated from message bsky.GetProfileRecordsRequest + */ +export class GetProfileRecordsRequest extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetProfileRecordsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetProfileRecordsRequest { + return new GetProfileRecordsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetProfileRecordsRequest { + return new GetProfileRecordsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetProfileRecordsRequest { + return new GetProfileRecordsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetProfileRecordsRequest + | PlainMessage + | undefined, + b: + | GetProfileRecordsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetProfileRecordsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetProfileRecordsResponse + */ +export class GetProfileRecordsResponse extends Message { + /** + * @generated from field: repeated bsky.Record records = 1; + */ + records: Record[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetProfileRecordsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'records', kind: 'message', T: Record, repeated: true }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetProfileRecordsResponse { + return new GetProfileRecordsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetProfileRecordsResponse { + return new GetProfileRecordsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetProfileRecordsResponse { + return new GetProfileRecordsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetProfileRecordsResponse + | PlainMessage + | undefined, + b: + | GetProfileRecordsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetProfileRecordsResponse, a, b) + } +} + +/** + * @generated from message bsky.GetRepostRecordsRequest + */ +export class GetRepostRecordsRequest extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetRepostRecordsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetRepostRecordsRequest { + return new GetRepostRecordsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetRepostRecordsRequest { + return new GetRepostRecordsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetRepostRecordsRequest { + return new GetRepostRecordsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetRepostRecordsRequest + | PlainMessage + | undefined, + b: + | GetRepostRecordsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetRepostRecordsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetRepostRecordsResponse + */ +export class GetRepostRecordsResponse extends Message { + /** + * @generated from field: repeated bsky.Record records = 1; + */ + records: Record[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetRepostRecordsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'records', kind: 'message', T: Record, repeated: true }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetRepostRecordsResponse { + return new GetRepostRecordsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetRepostRecordsResponse { + return new GetRepostRecordsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetRepostRecordsResponse { + return new GetRepostRecordsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetRepostRecordsResponse + | PlainMessage + | undefined, + b: + | GetRepostRecordsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetRepostRecordsResponse, a, b) + } +} + +/** + * @generated from message bsky.GetThreadGateRecordsRequest + */ +export class GetThreadGateRecordsRequest extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetThreadGateRecordsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetThreadGateRecordsRequest { + return new GetThreadGateRecordsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetThreadGateRecordsRequest { + return new GetThreadGateRecordsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetThreadGateRecordsRequest { + return new GetThreadGateRecordsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetThreadGateRecordsRequest + | PlainMessage + | undefined, + b: + | GetThreadGateRecordsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetThreadGateRecordsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetThreadGateRecordsResponse + */ +export class GetThreadGateRecordsResponse extends Message { + /** + * @generated from field: repeated bsky.Record records = 1; + */ + records: Record[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetThreadGateRecordsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'records', kind: 'message', T: Record, repeated: true }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetThreadGateRecordsResponse { + return new GetThreadGateRecordsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetThreadGateRecordsResponse { + return new GetThreadGateRecordsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetThreadGateRecordsResponse { + return new GetThreadGateRecordsResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetThreadGateRecordsResponse + | PlainMessage + | undefined, + b: + | GetThreadGateRecordsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetThreadGateRecordsResponse, a, b) + } +} + +/** + * - Return follow uris where user A follows users B, C, D, … + * - E.g. for viewer state on `getProfiles` + * + * @generated from message bsky.GetActorFollowsActorsRequest + */ +export class GetActorFollowsActorsRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: repeated string target_dids = 2; + */ + targetDids: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetActorFollowsActorsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { + no: 2, + name: 'target_dids', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetActorFollowsActorsRequest { + return new GetActorFollowsActorsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetActorFollowsActorsRequest { + return new GetActorFollowsActorsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetActorFollowsActorsRequest { + return new GetActorFollowsActorsRequest().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetActorFollowsActorsRequest + | PlainMessage + | undefined, + b: + | GetActorFollowsActorsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetActorFollowsActorsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetActorFollowsActorsResponse + */ +export class GetActorFollowsActorsResponse extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetActorFollowsActorsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetActorFollowsActorsResponse { + return new GetActorFollowsActorsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetActorFollowsActorsResponse { + return new GetActorFollowsActorsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetActorFollowsActorsResponse { + return new GetActorFollowsActorsResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetActorFollowsActorsResponse + | PlainMessage + | undefined, + b: + | GetActorFollowsActorsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetActorFollowsActorsResponse, a, b) + } +} + +/** + * - Return follow uris of users who follows user A + * - For `getFollowers` list + * + * @generated from message bsky.GetFollowersRequest + */ +export class GetFollowersRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetFollowersRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetFollowersRequest { + return new GetFollowersRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetFollowersRequest { + return new GetFollowersRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetFollowersRequest { + return new GetFollowersRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetFollowersRequest | PlainMessage | undefined, + b: GetFollowersRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetFollowersRequest, a, b) + } +} + +/** + * @generated from message bsky.FollowInfo + */ +export class FollowInfo extends Message { + /** + * @generated from field: string uri = 1; + */ + uri = '' + + /** + * @generated from field: string actor_did = 2; + */ + actorDid = '' + + /** + * @generated from field: string subject_did = 3; + */ + subjectDid = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.FollowInfo' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { + no: 3, + name: 'subject_did', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): FollowInfo { + return new FollowInfo().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): FollowInfo { + return new FollowInfo().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): FollowInfo { + return new FollowInfo().fromJsonString(jsonString, options) + } + + static equals( + a: FollowInfo | PlainMessage | undefined, + b: FollowInfo | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(FollowInfo, a, b) + } +} + +/** + * @generated from message bsky.GetFollowersResponse + */ +export class GetFollowersResponse extends Message { + /** + * @generated from field: repeated bsky.FollowInfo followers = 1; + */ + followers: FollowInfo[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetFollowersResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'followers', + kind: 'message', + T: FollowInfo, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetFollowersResponse { + return new GetFollowersResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetFollowersResponse { + return new GetFollowersResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetFollowersResponse { + return new GetFollowersResponse().fromJsonString(jsonString, options) + } + + static equals( + a: GetFollowersResponse | PlainMessage | undefined, + b: GetFollowersResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetFollowersResponse, a, b) + } +} + +/** + * - Return follow uris of users A follows + * - For `getFollows` list + * + * @generated from message bsky.GetFollowsRequest + */ +export class GetFollowsRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetFollowsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetFollowsRequest { + return new GetFollowsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetFollowsRequest { + return new GetFollowsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetFollowsRequest { + return new GetFollowsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetFollowsRequest | PlainMessage | undefined, + b: GetFollowsRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetFollowsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetFollowsResponse + */ +export class GetFollowsResponse extends Message { + /** + * @generated from field: repeated bsky.FollowInfo follows = 1; + */ + follows: FollowInfo[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetFollowsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'follows', kind: 'message', T: FollowInfo, repeated: true }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetFollowsResponse { + return new GetFollowsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetFollowsResponse { + return new GetFollowsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetFollowsResponse { + return new GetFollowsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: GetFollowsResponse | PlainMessage | undefined, + b: GetFollowsResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetFollowsResponse, a, b) + } +} + +/** + * - return like uris where subject uri is subject A + * - `getLikes` list for a post + * + * @generated from message bsky.GetLikesBySubjectRequest + */ +export class GetLikesBySubjectRequest extends Message { + /** + * @generated from field: bsky.RecordRef subject = 1; + */ + subject?: RecordRef + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetLikesBySubjectRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'subject', kind: 'message', T: RecordRef }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetLikesBySubjectRequest { + return new GetLikesBySubjectRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetLikesBySubjectRequest { + return new GetLikesBySubjectRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetLikesBySubjectRequest { + return new GetLikesBySubjectRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetLikesBySubjectRequest + | PlainMessage + | undefined, + b: + | GetLikesBySubjectRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetLikesBySubjectRequest, a, b) + } +} + +/** + * @generated from message bsky.GetLikesBySubjectResponse + */ +export class GetLikesBySubjectResponse extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetLikesBySubjectResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetLikesBySubjectResponse { + return new GetLikesBySubjectResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetLikesBySubjectResponse { + return new GetLikesBySubjectResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetLikesBySubjectResponse { + return new GetLikesBySubjectResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetLikesBySubjectResponse + | PlainMessage + | undefined, + b: + | GetLikesBySubjectResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetLikesBySubjectResponse, a, b) + } +} + +/** + * - return like uris for user A on subject B, C, D... + * - viewer state on posts + * + * @generated from message bsky.GetLikesByActorAndSubjectsRequest + */ +export class GetLikesByActorAndSubjectsRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: repeated bsky.RecordRef refs = 2; + */ + refs: RecordRef[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetLikesByActorAndSubjectsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'refs', kind: 'message', T: RecordRef, repeated: true }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetLikesByActorAndSubjectsRequest { + return new GetLikesByActorAndSubjectsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetLikesByActorAndSubjectsRequest { + return new GetLikesByActorAndSubjectsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetLikesByActorAndSubjectsRequest { + return new GetLikesByActorAndSubjectsRequest().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetLikesByActorAndSubjectsRequest + | PlainMessage + | undefined, + b: + | GetLikesByActorAndSubjectsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetLikesByActorAndSubjectsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetLikesByActorAndSubjectsResponse + */ +export class GetLikesByActorAndSubjectsResponse extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetLikesByActorAndSubjectsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetLikesByActorAndSubjectsResponse { + return new GetLikesByActorAndSubjectsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetLikesByActorAndSubjectsResponse { + return new GetLikesByActorAndSubjectsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetLikesByActorAndSubjectsResponse { + return new GetLikesByActorAndSubjectsResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetLikesByActorAndSubjectsResponse + | PlainMessage + | undefined, + b: + | GetLikesByActorAndSubjectsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetLikesByActorAndSubjectsResponse, a, b) + } +} + +/** + * - return recent like uris for user A + * - `getActorLikes` list for a user + * + * @generated from message bsky.GetActorLikesRequest + */ +export class GetActorLikesRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetActorLikesRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetActorLikesRequest { + return new GetActorLikesRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetActorLikesRequest { + return new GetActorLikesRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetActorLikesRequest { + return new GetActorLikesRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetActorLikesRequest | PlainMessage | undefined, + b: GetActorLikesRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetActorLikesRequest, a, b) + } +} + +/** + * @generated from message bsky.LikeInfo + */ +export class LikeInfo extends Message { + /** + * @generated from field: string uri = 1; + */ + uri = '' + + /** + * @generated from field: string subject = 2; + */ + subject = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.LikeInfo' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'subject', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): LikeInfo { + return new LikeInfo().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): LikeInfo { + return new LikeInfo().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): LikeInfo { + return new LikeInfo().fromJsonString(jsonString, options) + } + + static equals( + a: LikeInfo | PlainMessage | undefined, + b: LikeInfo | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(LikeInfo, a, b) + } +} + +/** + * @generated from message bsky.GetActorLikesResponse + */ +export class GetActorLikesResponse extends Message { + /** + * @generated from field: repeated bsky.LikeInfo likes = 1; + */ + likes: LikeInfo[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetActorLikesResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'likes', kind: 'message', T: LikeInfo, repeated: true }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetActorLikesResponse { + return new GetActorLikesResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetActorLikesResponse { + return new GetActorLikesResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetActorLikesResponse { + return new GetActorLikesResponse().fromJsonString(jsonString, options) + } + + static equals( + a: GetActorLikesResponse | PlainMessage | undefined, + b: GetActorLikesResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetActorLikesResponse, a, b) + } +} + +/** + * + * Interactions + * + * + * @generated from message bsky.GetInteractionCountsRequest + */ +export class GetInteractionCountsRequest extends Message { + /** + * @generated from field: repeated bsky.RecordRef refs = 1; + */ + refs: RecordRef[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetInteractionCountsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'refs', kind: 'message', T: RecordRef, repeated: true }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetInteractionCountsRequest { + return new GetInteractionCountsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetInteractionCountsRequest { + return new GetInteractionCountsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetInteractionCountsRequest { + return new GetInteractionCountsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetInteractionCountsRequest + | PlainMessage + | undefined, + b: + | GetInteractionCountsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetInteractionCountsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetInteractionCountsResponse + */ +export class GetInteractionCountsResponse extends Message { + /** + * @generated from field: repeated int32 likes = 1; + */ + likes: number[] = [] + + /** + * @generated from field: repeated int32 reposts = 2; + */ + reposts: number[] = [] + + /** + * @generated from field: repeated int32 replies = 3; + */ + replies: number[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetInteractionCountsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'likes', + kind: 'scalar', + T: 5 /* ScalarType.INT32 */, + repeated: true, + }, + { + no: 2, + name: 'reposts', + kind: 'scalar', + T: 5 /* ScalarType.INT32 */, + repeated: true, + }, + { + no: 3, + name: 'replies', + kind: 'scalar', + T: 5 /* ScalarType.INT32 */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetInteractionCountsResponse { + return new GetInteractionCountsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetInteractionCountsResponse { + return new GetInteractionCountsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetInteractionCountsResponse { + return new GetInteractionCountsResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetInteractionCountsResponse + | PlainMessage + | undefined, + b: + | GetInteractionCountsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetInteractionCountsResponse, a, b) + } +} + +/** + * @generated from message bsky.GetCountsForUsersRequest + */ +export class GetCountsForUsersRequest extends Message { + /** + * @generated from field: repeated string dids = 1; + */ + dids: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetCountsForUsersRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'dids', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetCountsForUsersRequest { + return new GetCountsForUsersRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetCountsForUsersRequest { + return new GetCountsForUsersRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetCountsForUsersRequest { + return new GetCountsForUsersRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetCountsForUsersRequest + | PlainMessage + | undefined, + b: + | GetCountsForUsersRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetCountsForUsersRequest, a, b) + } +} + +/** + * @generated from message bsky.GetCountsForUsersResponse + */ +export class GetCountsForUsersResponse extends Message { + /** + * @generated from field: repeated int32 posts = 1; + */ + posts: number[] = [] + + /** + * @generated from field: repeated int32 reposts = 2; + */ + reposts: number[] = [] + + /** + * @generated from field: repeated int32 following = 3; + */ + following: number[] = [] + + /** + * @generated from field: repeated int32 followers = 4; + */ + followers: number[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetCountsForUsersResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'posts', + kind: 'scalar', + T: 5 /* ScalarType.INT32 */, + repeated: true, + }, + { + no: 2, + name: 'reposts', + kind: 'scalar', + T: 5 /* ScalarType.INT32 */, + repeated: true, + }, + { + no: 3, + name: 'following', + kind: 'scalar', + T: 5 /* ScalarType.INT32 */, + repeated: true, + }, + { + no: 4, + name: 'followers', + kind: 'scalar', + T: 5 /* ScalarType.INT32 */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetCountsForUsersResponse { + return new GetCountsForUsersResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetCountsForUsersResponse { + return new GetCountsForUsersResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetCountsForUsersResponse { + return new GetCountsForUsersResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetCountsForUsersResponse + | PlainMessage + | undefined, + b: + | GetCountsForUsersResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetCountsForUsersResponse, a, b) + } +} + +/** + * - return repost uris where subject uri is subject A + * - `getReposts` list for a post + * + * @generated from message bsky.GetRepostsBySubjectRequest + */ +export class GetRepostsBySubjectRequest extends Message { + /** + * @generated from field: bsky.RecordRef subject = 1; + */ + subject?: RecordRef + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetRepostsBySubjectRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'subject', kind: 'message', T: RecordRef }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetRepostsBySubjectRequest { + return new GetRepostsBySubjectRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetRepostsBySubjectRequest { + return new GetRepostsBySubjectRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetRepostsBySubjectRequest { + return new GetRepostsBySubjectRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetRepostsBySubjectRequest + | PlainMessage + | undefined, + b: + | GetRepostsBySubjectRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetRepostsBySubjectRequest, a, b) + } +} + +/** + * @generated from message bsky.GetRepostsBySubjectResponse + */ +export class GetRepostsBySubjectResponse extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetRepostsBySubjectResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetRepostsBySubjectResponse { + return new GetRepostsBySubjectResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetRepostsBySubjectResponse { + return new GetRepostsBySubjectResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetRepostsBySubjectResponse { + return new GetRepostsBySubjectResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetRepostsBySubjectResponse + | PlainMessage + | undefined, + b: + | GetRepostsBySubjectResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetRepostsBySubjectResponse, a, b) + } +} + +/** + * - return repost uris for user A on subject B, C, D... + * - viewer state on posts + * + * @generated from message bsky.GetRepostsByActorAndSubjectsRequest + */ +export class GetRepostsByActorAndSubjectsRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: repeated bsky.RecordRef refs = 2; + */ + refs: RecordRef[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetRepostsByActorAndSubjectsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'refs', kind: 'message', T: RecordRef, repeated: true }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetRepostsByActorAndSubjectsRequest { + return new GetRepostsByActorAndSubjectsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetRepostsByActorAndSubjectsRequest { + return new GetRepostsByActorAndSubjectsRequest().fromJson( + jsonValue, + options, + ) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetRepostsByActorAndSubjectsRequest { + return new GetRepostsByActorAndSubjectsRequest().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetRepostsByActorAndSubjectsRequest + | PlainMessage + | undefined, + b: + | GetRepostsByActorAndSubjectsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetRepostsByActorAndSubjectsRequest, a, b) + } +} + +/** + * @generated from message bsky.RecordRef + */ +export class RecordRef extends Message { + /** + * @generated from field: string uri = 1; + */ + uri = '' + + /** + * @generated from field: string cid = 2; + */ + cid = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.RecordRef' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'cid', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): RecordRef { + return new RecordRef().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): RecordRef { + return new RecordRef().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): RecordRef { + return new RecordRef().fromJsonString(jsonString, options) + } + + static equals( + a: RecordRef | PlainMessage | undefined, + b: RecordRef | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(RecordRef, a, b) + } +} + +/** + * @generated from message bsky.GetRepostsByActorAndSubjectsResponse + */ +export class GetRepostsByActorAndSubjectsResponse extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetRepostsByActorAndSubjectsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetRepostsByActorAndSubjectsResponse { + return new GetRepostsByActorAndSubjectsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetRepostsByActorAndSubjectsResponse { + return new GetRepostsByActorAndSubjectsResponse().fromJson( + jsonValue, + options, + ) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetRepostsByActorAndSubjectsResponse { + return new GetRepostsByActorAndSubjectsResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetRepostsByActorAndSubjectsResponse + | PlainMessage + | undefined, + b: + | GetRepostsByActorAndSubjectsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetRepostsByActorAndSubjectsResponse, a, b) + } +} + +/** + * - return recent repost uris for user A + * - `getActorReposts` list for a user + * + * @generated from message bsky.GetActorRepostsRequest + */ +export class GetActorRepostsRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetActorRepostsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetActorRepostsRequest { + return new GetActorRepostsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetActorRepostsRequest { + return new GetActorRepostsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetActorRepostsRequest { + return new GetActorRepostsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetActorRepostsRequest + | PlainMessage + | undefined, + b: + | GetActorRepostsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetActorRepostsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetActorRepostsResponse + */ +export class GetActorRepostsResponse extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetActorRepostsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetActorRepostsResponse { + return new GetActorRepostsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetActorRepostsResponse { + return new GetActorRepostsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetActorRepostsResponse { + return new GetActorRepostsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetActorRepostsResponse + | PlainMessage + | undefined, + b: + | GetActorRepostsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetActorRepostsResponse, a, b) + } +} + +/** + * - return actor information for dids A, B, C… + * - profile hydration + * - should this include handles? apply repo takedown? + * + * @generated from message bsky.GetActorsRequest + */ +export class GetActorsRequest extends Message { + /** + * @generated from field: repeated string dids = 1; + */ + dids: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetActorsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'dids', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetActorsRequest { + return new GetActorsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetActorsRequest { + return new GetActorsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetActorsRequest { + return new GetActorsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetActorsRequest | PlainMessage | undefined, + b: GetActorsRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetActorsRequest, a, b) + } +} + +/** + * @generated from message bsky.ActorInfo + */ +export class ActorInfo extends Message { + /** + * @generated from field: bool exists = 1; + */ + exists = false + + /** + * @generated from field: string handle = 2; + */ + handle = '' + + /** + * @generated from field: bsky.Record profile = 3; + */ + profile?: Record + + /** + * @generated from field: bool taken_down = 4; + */ + takenDown = false + + /** + * @generated from field: string takedown_ref = 5; + */ + takedownRef = '' + + /** + * @generated from field: google.protobuf.Timestamp tombstoned_at = 6; + */ + tombstonedAt?: Timestamp + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.ActorInfo' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'exists', kind: 'scalar', T: 8 /* ScalarType.BOOL */ }, + { no: 2, name: 'handle', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 3, name: 'profile', kind: 'message', T: Record }, + { no: 4, name: 'taken_down', kind: 'scalar', T: 8 /* ScalarType.BOOL */ }, + { + no: 5, + name: 'takedown_ref', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + { no: 6, name: 'tombstoned_at', kind: 'message', T: Timestamp }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): ActorInfo { + return new ActorInfo().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): ActorInfo { + return new ActorInfo().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): ActorInfo { + return new ActorInfo().fromJsonString(jsonString, options) + } + + static equals( + a: ActorInfo | PlainMessage | undefined, + b: ActorInfo | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(ActorInfo, a, b) + } +} + +/** + * @generated from message bsky.GetActorsResponse + */ +export class GetActorsResponse extends Message { + /** + * @generated from field: repeated bsky.ActorInfo actors = 1; + */ + actors: ActorInfo[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetActorsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actors', kind: 'message', T: ActorInfo, repeated: true }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetActorsResponse { + return new GetActorsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetActorsResponse { + return new GetActorsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetActorsResponse { + return new GetActorsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: GetActorsResponse | PlainMessage | undefined, + b: GetActorsResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetActorsResponse, a, b) + } +} + +/** + * - return did for handle A + * - `resolveHandle` + * - answering queries where the query param is a handle + * + * @generated from message bsky.GetDidsByHandlesRequest + */ +export class GetDidsByHandlesRequest extends Message { + /** + * @generated from field: repeated string handles = 1; + */ + handles: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetDidsByHandlesRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'handles', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetDidsByHandlesRequest { + return new GetDidsByHandlesRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetDidsByHandlesRequest { + return new GetDidsByHandlesRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetDidsByHandlesRequest { + return new GetDidsByHandlesRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetDidsByHandlesRequest + | PlainMessage + | undefined, + b: + | GetDidsByHandlesRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetDidsByHandlesRequest, a, b) + } +} + +/** + * @generated from message bsky.GetDidsByHandlesResponse + */ +export class GetDidsByHandlesResponse extends Message { + /** + * @generated from field: repeated string dids = 1; + */ + dids: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetDidsByHandlesResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'dids', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetDidsByHandlesResponse { + return new GetDidsByHandlesResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetDidsByHandlesResponse { + return new GetDidsByHandlesResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetDidsByHandlesResponse { + return new GetDidsByHandlesResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetDidsByHandlesResponse + | PlainMessage + | undefined, + b: + | GetDidsByHandlesResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetDidsByHandlesResponse, a, b) + } +} + +/** + * - return relationships between user A and users B, C, D... + * - profile hydration + * - block application + * + * @generated from message bsky.GetRelationshipsRequest + */ +export class GetRelationshipsRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: repeated string target_dids = 2; + */ + targetDids: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetRelationshipsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { + no: 2, + name: 'target_dids', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetRelationshipsRequest { + return new GetRelationshipsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetRelationshipsRequest { + return new GetRelationshipsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetRelationshipsRequest { + return new GetRelationshipsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetRelationshipsRequest + | PlainMessage + | undefined, + b: + | GetRelationshipsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetRelationshipsRequest, a, b) + } +} + +/** + * @generated from message bsky.Relationships + */ +export class Relationships extends Message { + /** + * @generated from field: bool muted = 1; + */ + muted = false + + /** + * @generated from field: string muted_by_list = 2; + */ + mutedByList = '' + + /** + * @generated from field: string blocked_by = 3; + */ + blockedBy = '' + + /** + * @generated from field: string blocking = 4; + */ + blocking = '' + + /** + * @generated from field: string blocked_by_list = 5; + */ + blockedByList = '' + + /** + * @generated from field: string blocking_by_list = 6; + */ + blockingByList = '' + + /** + * @generated from field: string following = 7; + */ + following = '' + + /** + * @generated from field: string followed_by = 8; + */ + followedBy = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.Relationships' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'muted', kind: 'scalar', T: 8 /* ScalarType.BOOL */ }, + { + no: 2, + name: 'muted_by_list', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + { no: 3, name: 'blocked_by', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 4, name: 'blocking', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { + no: 5, + name: 'blocked_by_list', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + { + no: 6, + name: 'blocking_by_list', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + { no: 7, name: 'following', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { + no: 8, + name: 'followed_by', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): Relationships { + return new Relationships().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): Relationships { + return new Relationships().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): Relationships { + return new Relationships().fromJsonString(jsonString, options) + } + + static equals( + a: Relationships | PlainMessage | undefined, + b: Relationships | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(Relationships, a, b) + } +} + +/** + * @generated from message bsky.GetRelationshipsResponse + */ +export class GetRelationshipsResponse extends Message { + /** + * @generated from field: repeated bsky.Relationships relationships = 1; + */ + relationships: Relationships[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetRelationshipsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'relationships', + kind: 'message', + T: Relationships, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetRelationshipsResponse { + return new GetRelationshipsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetRelationshipsResponse { + return new GetRelationshipsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetRelationshipsResponse { + return new GetRelationshipsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetRelationshipsResponse + | PlainMessage + | undefined, + b: + | GetRelationshipsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetRelationshipsResponse, a, b) + } +} + +/** + * - return whether a block (bidrectionally and either direct or through a list) exists between two dids + * - enforcing 3rd party block violations + * + * @generated from message bsky.RelationshipPair + */ +export class RelationshipPair extends Message { + /** + * @generated from field: string a = 1; + */ + a = '' + + /** + * @generated from field: string b = 2; + */ + b = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.RelationshipPair' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'a', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'b', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): RelationshipPair { + return new RelationshipPair().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): RelationshipPair { + return new RelationshipPair().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): RelationshipPair { + return new RelationshipPair().fromJsonString(jsonString, options) + } + + static equals( + a: RelationshipPair | PlainMessage | undefined, + b: RelationshipPair | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(RelationshipPair, a, b) + } +} + +/** + * @generated from message bsky.GetBlockExistenceRequest + */ +export class GetBlockExistenceRequest extends Message { + /** + * @generated from field: repeated bsky.RelationshipPair pairs = 1; + */ + pairs: RelationshipPair[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetBlockExistenceRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'pairs', + kind: 'message', + T: RelationshipPair, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetBlockExistenceRequest { + return new GetBlockExistenceRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetBlockExistenceRequest { + return new GetBlockExistenceRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetBlockExistenceRequest { + return new GetBlockExistenceRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetBlockExistenceRequest + | PlainMessage + | undefined, + b: + | GetBlockExistenceRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetBlockExistenceRequest, a, b) + } +} + +/** + * @generated from message bsky.GetBlockExistenceResponse + */ +export class GetBlockExistenceResponse extends Message { + /** + * @generated from field: repeated bool exists = 1; + */ + exists: boolean[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetBlockExistenceResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'exists', + kind: 'scalar', + T: 8 /* ScalarType.BOOL */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetBlockExistenceResponse { + return new GetBlockExistenceResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetBlockExistenceResponse { + return new GetBlockExistenceResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetBlockExistenceResponse { + return new GetBlockExistenceResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetBlockExistenceResponse + | PlainMessage + | undefined, + b: + | GetBlockExistenceResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetBlockExistenceResponse, a, b) + } +} + +/** + * @generated from message bsky.ListItemInfo + */ +export class ListItemInfo extends Message { + /** + * @generated from field: string uri = 1; + */ + uri = '' + + /** + * @generated from field: string did = 2; + */ + did = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.ListItemInfo' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): ListItemInfo { + return new ListItemInfo().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): ListItemInfo { + return new ListItemInfo().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): ListItemInfo { + return new ListItemInfo().fromJsonString(jsonString, options) + } + + static equals( + a: ListItemInfo | PlainMessage | undefined, + b: ListItemInfo | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(ListItemInfo, a, b) + } +} + +/** + * - Return dids of users in list A + * - E.g. to view items in one of your mute lists + * + * @generated from message bsky.GetListMembersRequest + */ +export class GetListMembersRequest extends Message { + /** + * @generated from field: string list_uri = 1; + */ + listUri = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetListMembersRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'list_uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetListMembersRequest { + return new GetListMembersRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetListMembersRequest { + return new GetListMembersRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetListMembersRequest { + return new GetListMembersRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetListMembersRequest | PlainMessage | undefined, + b: GetListMembersRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetListMembersRequest, a, b) + } +} + +/** + * @generated from message bsky.GetListMembersResponse + */ +export class GetListMembersResponse extends Message { + /** + * @generated from field: repeated bsky.ListItemInfo listitems = 1; + */ + listitems: ListItemInfo[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetListMembersResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'listitems', + kind: 'message', + T: ListItemInfo, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetListMembersResponse { + return new GetListMembersResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetListMembersResponse { + return new GetListMembersResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetListMembersResponse { + return new GetListMembersResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetListMembersResponse + | PlainMessage + | undefined, + b: + | GetListMembersResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetListMembersResponse, a, b) + } +} + +/** + * - Return list uris where user A in list B, C, D… + * - Used in thread reply gates + * + * @generated from message bsky.GetListMembershipRequest + */ +export class GetListMembershipRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: repeated string list_uris = 2; + */ + listUris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetListMembershipRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { + no: 2, + name: 'list_uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetListMembershipRequest { + return new GetListMembershipRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetListMembershipRequest { + return new GetListMembershipRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetListMembershipRequest { + return new GetListMembershipRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetListMembershipRequest + | PlainMessage + | undefined, + b: + | GetListMembershipRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetListMembershipRequest, a, b) + } +} + +/** + * @generated from message bsky.GetListMembershipResponse + */ +export class GetListMembershipResponse extends Message { + /** + * @generated from field: repeated string listitem_uris = 1; + */ + listitemUris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetListMembershipResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'listitem_uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetListMembershipResponse { + return new GetListMembershipResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetListMembershipResponse { + return new GetListMembershipResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetListMembershipResponse { + return new GetListMembershipResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetListMembershipResponse + | PlainMessage + | undefined, + b: + | GetListMembershipResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetListMembershipResponse, a, b) + } +} + +/** + * - Return number of items in list A + * - For aggregate + * + * @generated from message bsky.GetListCountRequest + */ +export class GetListCountRequest extends Message { + /** + * @generated from field: string list_uri = 1; + */ + listUri = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetListCountRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'list_uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetListCountRequest { + return new GetListCountRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetListCountRequest { + return new GetListCountRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetListCountRequest { + return new GetListCountRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetListCountRequest | PlainMessage | undefined, + b: GetListCountRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetListCountRequest, a, b) + } +} + +/** + * @generated from message bsky.GetListCountResponse + */ +export class GetListCountResponse extends Message { + /** + * @generated from field: int32 count = 1; + */ + count = 0 + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetListCountResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'count', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetListCountResponse { + return new GetListCountResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetListCountResponse { + return new GetListCountResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetListCountResponse { + return new GetListCountResponse().fromJsonString(jsonString, options) + } + + static equals( + a: GetListCountResponse | PlainMessage | undefined, + b: GetListCountResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetListCountResponse, a, b) + } +} + +/** + * - return list of uris of lists created by A + * - `getLists` + * + * @generated from message bsky.GetActorListsRequest + */ +export class GetActorListsRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetActorListsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetActorListsRequest { + return new GetActorListsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetActorListsRequest { + return new GetActorListsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetActorListsRequest { + return new GetActorListsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetActorListsRequest | PlainMessage | undefined, + b: GetActorListsRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetActorListsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetActorListsResponse + */ +export class GetActorListsResponse extends Message { + /** + * @generated from field: repeated string list_uris = 1; + */ + listUris: string[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetActorListsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'list_uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetActorListsResponse { + return new GetActorListsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetActorListsResponse { + return new GetActorListsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetActorListsResponse { + return new GetActorListsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: GetActorListsResponse | PlainMessage | undefined, + b: GetActorListsResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetActorListsResponse, a, b) + } +} + +/** + * - return boolean if user A has muted user B + * - hydrating mute state onto profiles + * + * @generated from message bsky.GetActorMutesActorRequest + */ +export class GetActorMutesActorRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: string target_did = 2; + */ + targetDid = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetActorMutesActorRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'target_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetActorMutesActorRequest { + return new GetActorMutesActorRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetActorMutesActorRequest { + return new GetActorMutesActorRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetActorMutesActorRequest { + return new GetActorMutesActorRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetActorMutesActorRequest + | PlainMessage + | undefined, + b: + | GetActorMutesActorRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetActorMutesActorRequest, a, b) + } +} + +/** + * @generated from message bsky.GetActorMutesActorResponse + */ +export class GetActorMutesActorResponse extends Message { + /** + * @generated from field: bool muted = 1; + */ + muted = false + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetActorMutesActorResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'muted', kind: 'scalar', T: 8 /* ScalarType.BOOL */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetActorMutesActorResponse { + return new GetActorMutesActorResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetActorMutesActorResponse { + return new GetActorMutesActorResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetActorMutesActorResponse { + return new GetActorMutesActorResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetActorMutesActorResponse + | PlainMessage + | undefined, + b: + | GetActorMutesActorResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetActorMutesActorResponse, a, b) + } +} + +/** + * - return list of user dids of users who A mutes + * - `getMutes` + * + * @generated from message bsky.GetMutesRequest + */ +export class GetMutesRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetMutesRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetMutesRequest { + return new GetMutesRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetMutesRequest { + return new GetMutesRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetMutesRequest { + return new GetMutesRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetMutesRequest | PlainMessage | undefined, + b: GetMutesRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetMutesRequest, a, b) + } +} + +/** + * @generated from message bsky.GetMutesResponse + */ +export class GetMutesResponse extends Message { + /** + * @generated from field: repeated string dids = 1; + */ + dids: string[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetMutesResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'dids', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetMutesResponse { + return new GetMutesResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetMutesResponse { + return new GetMutesResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetMutesResponse { + return new GetMutesResponse().fromJsonString(jsonString, options) + } + + static equals( + a: GetMutesResponse | PlainMessage | undefined, + b: GetMutesResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetMutesResponse, a, b) + } +} + +/** + * - return list uri of *any* list through which user A has muted user B + * - hydrating mute state onto profiles + * - note: we only need *one* uri even if a user is muted by multiple lists + * + * @generated from message bsky.GetActorMutesActorViaListRequest + */ +export class GetActorMutesActorViaListRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: string target_did = 2; + */ + targetDid = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetActorMutesActorViaListRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'target_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetActorMutesActorViaListRequest { + return new GetActorMutesActorViaListRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetActorMutesActorViaListRequest { + return new GetActorMutesActorViaListRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetActorMutesActorViaListRequest { + return new GetActorMutesActorViaListRequest().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetActorMutesActorViaListRequest + | PlainMessage + | undefined, + b: + | GetActorMutesActorViaListRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetActorMutesActorViaListRequest, a, b) + } +} + +/** + * @generated from message bsky.GetActorMutesActorViaListResponse + */ +export class GetActorMutesActorViaListResponse extends Message { + /** + * @generated from field: string list_uri = 1; + */ + listUri = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetActorMutesActorViaListResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'list_uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetActorMutesActorViaListResponse { + return new GetActorMutesActorViaListResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetActorMutesActorViaListResponse { + return new GetActorMutesActorViaListResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetActorMutesActorViaListResponse { + return new GetActorMutesActorViaListResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetActorMutesActorViaListResponse + | PlainMessage + | undefined, + b: + | GetActorMutesActorViaListResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetActorMutesActorViaListResponse, a, b) + } +} + +/** + * - return boolean if actor A has subscribed to mutelist B + * - list view hydration + * + * @generated from message bsky.GetMutelistSubscriptionRequest + */ +export class GetMutelistSubscriptionRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: string list_uri = 2; + */ + listUri = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetMutelistSubscriptionRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'list_uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetMutelistSubscriptionRequest { + return new GetMutelistSubscriptionRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetMutelistSubscriptionRequest { + return new GetMutelistSubscriptionRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetMutelistSubscriptionRequest { + return new GetMutelistSubscriptionRequest().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetMutelistSubscriptionRequest + | PlainMessage + | undefined, + b: + | GetMutelistSubscriptionRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetMutelistSubscriptionRequest, a, b) + } +} + +/** + * @generated from message bsky.GetMutelistSubscriptionResponse + */ +export class GetMutelistSubscriptionResponse extends Message { + /** + * @generated from field: bool subscribed = 1; + */ + subscribed = false + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetMutelistSubscriptionResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'subscribed', kind: 'scalar', T: 8 /* ScalarType.BOOL */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetMutelistSubscriptionResponse { + return new GetMutelistSubscriptionResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetMutelistSubscriptionResponse { + return new GetMutelistSubscriptionResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetMutelistSubscriptionResponse { + return new GetMutelistSubscriptionResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetMutelistSubscriptionResponse + | PlainMessage + | undefined, + b: + | GetMutelistSubscriptionResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetMutelistSubscriptionResponse, a, b) + } +} + +/** + * - return list of list uris of mutelists that A subscribes to + * - `getListMutes` + * + * @generated from message bsky.GetMutelistSubscriptionsRequest + */ +export class GetMutelistSubscriptionsRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetMutelistSubscriptionsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetMutelistSubscriptionsRequest { + return new GetMutelistSubscriptionsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetMutelistSubscriptionsRequest { + return new GetMutelistSubscriptionsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetMutelistSubscriptionsRequest { + return new GetMutelistSubscriptionsRequest().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetMutelistSubscriptionsRequest + | PlainMessage + | undefined, + b: + | GetMutelistSubscriptionsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetMutelistSubscriptionsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetMutelistSubscriptionsResponse + */ +export class GetMutelistSubscriptionsResponse extends Message { + /** + * @generated from field: repeated string list_uris = 1; + */ + listUris: string[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetMutelistSubscriptionsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'list_uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetMutelistSubscriptionsResponse { + return new GetMutelistSubscriptionsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetMutelistSubscriptionsResponse { + return new GetMutelistSubscriptionsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetMutelistSubscriptionsResponse { + return new GetMutelistSubscriptionsResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetMutelistSubscriptionsResponse + | PlainMessage + | undefined, + b: + | GetMutelistSubscriptionsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetMutelistSubscriptionsResponse, a, b) + } +} + +/** + * - Return block uri if there is a block between users A & B (bidirectional) + * - hydrating (& actioning) block state on profiles + * - handling 3rd party blocks + * + * @generated from message bsky.GetBidirectionalBlockRequest + */ +export class GetBidirectionalBlockRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: string target_did = 2; + */ + targetDid = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetBidirectionalBlockRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'target_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetBidirectionalBlockRequest { + return new GetBidirectionalBlockRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetBidirectionalBlockRequest { + return new GetBidirectionalBlockRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetBidirectionalBlockRequest { + return new GetBidirectionalBlockRequest().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetBidirectionalBlockRequest + | PlainMessage + | undefined, + b: + | GetBidirectionalBlockRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetBidirectionalBlockRequest, a, b) + } +} + +/** + * @generated from message bsky.GetBidirectionalBlockResponse + */ +export class GetBidirectionalBlockResponse extends Message { + /** + * @generated from field: string block_uri = 1; + */ + blockUri = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetBidirectionalBlockResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'block_uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetBidirectionalBlockResponse { + return new GetBidirectionalBlockResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetBidirectionalBlockResponse { + return new GetBidirectionalBlockResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetBidirectionalBlockResponse { + return new GetBidirectionalBlockResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetBidirectionalBlockResponse + | PlainMessage + | undefined, + b: + | GetBidirectionalBlockResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetBidirectionalBlockResponse, a, b) + } +} + +/** + * - Return list of block uris and user dids of users who A blocks + * - `getBlocks` + * + * @generated from message bsky.GetBlocksRequest + */ +export class GetBlocksRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetBlocksRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetBlocksRequest { + return new GetBlocksRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetBlocksRequest { + return new GetBlocksRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetBlocksRequest { + return new GetBlocksRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetBlocksRequest | PlainMessage | undefined, + b: GetBlocksRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetBlocksRequest, a, b) + } +} + +/** + * @generated from message bsky.GetBlocksResponse + */ +export class GetBlocksResponse extends Message { + /** + * @generated from field: repeated string block_uris = 1; + */ + blockUris: string[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetBlocksResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'block_uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetBlocksResponse { + return new GetBlocksResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetBlocksResponse { + return new GetBlocksResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetBlocksResponse { + return new GetBlocksResponse().fromJsonString(jsonString, options) + } + + static equals( + a: GetBlocksResponse | PlainMessage | undefined, + b: GetBlocksResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetBlocksResponse, a, b) + } +} + +/** + * - Return list uri of ***any*** list through which users A & B have a block (bidirectional) + * - hydrating (& actioning) block state on profiles + * - handling 3rd party blocks + * + * @generated from message bsky.GetBidirectionalBlockViaListRequest + */ +export class GetBidirectionalBlockViaListRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: string target_did = 2; + */ + targetDid = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetBidirectionalBlockViaListRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'target_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetBidirectionalBlockViaListRequest { + return new GetBidirectionalBlockViaListRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetBidirectionalBlockViaListRequest { + return new GetBidirectionalBlockViaListRequest().fromJson( + jsonValue, + options, + ) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetBidirectionalBlockViaListRequest { + return new GetBidirectionalBlockViaListRequest().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetBidirectionalBlockViaListRequest + | PlainMessage + | undefined, + b: + | GetBidirectionalBlockViaListRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetBidirectionalBlockViaListRequest, a, b) + } +} + +/** + * @generated from message bsky.GetBidirectionalBlockViaListResponse + */ +export class GetBidirectionalBlockViaListResponse extends Message { + /** + * @generated from field: string list_uri = 1; + */ + listUri = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetBidirectionalBlockViaListResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'list_uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetBidirectionalBlockViaListResponse { + return new GetBidirectionalBlockViaListResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetBidirectionalBlockViaListResponse { + return new GetBidirectionalBlockViaListResponse().fromJson( + jsonValue, + options, + ) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetBidirectionalBlockViaListResponse { + return new GetBidirectionalBlockViaListResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetBidirectionalBlockViaListResponse + | PlainMessage + | undefined, + b: + | GetBidirectionalBlockViaListResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetBidirectionalBlockViaListResponse, a, b) + } +} + +/** + * - return boolean if user A has subscribed to blocklist B + * - list view hydration + * + * @generated from message bsky.GetBlocklistSubscriptionRequest + */ +export class GetBlocklistSubscriptionRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: string list_uri = 2; + */ + listUri = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetBlocklistSubscriptionRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'list_uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetBlocklistSubscriptionRequest { + return new GetBlocklistSubscriptionRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetBlocklistSubscriptionRequest { + return new GetBlocklistSubscriptionRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetBlocklistSubscriptionRequest { + return new GetBlocklistSubscriptionRequest().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetBlocklistSubscriptionRequest + | PlainMessage + | undefined, + b: + | GetBlocklistSubscriptionRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetBlocklistSubscriptionRequest, a, b) + } +} + +/** + * @generated from message bsky.GetBlocklistSubscriptionResponse + */ +export class GetBlocklistSubscriptionResponse extends Message { + /** + * @generated from field: string listblock_uri = 1; + */ + listblockUri = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetBlocklistSubscriptionResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'listblock_uri', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetBlocklistSubscriptionResponse { + return new GetBlocklistSubscriptionResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetBlocklistSubscriptionResponse { + return new GetBlocklistSubscriptionResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetBlocklistSubscriptionResponse { + return new GetBlocklistSubscriptionResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetBlocklistSubscriptionResponse + | PlainMessage + | undefined, + b: + | GetBlocklistSubscriptionResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetBlocklistSubscriptionResponse, a, b) + } +} + +/** + * - return list of list uris of Blockslists that A subscribes to + * - `getListBlocks` + * + * @generated from message bsky.GetBlocklistSubscriptionsRequest + */ +export class GetBlocklistSubscriptionsRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetBlocklistSubscriptionsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetBlocklistSubscriptionsRequest { + return new GetBlocklistSubscriptionsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetBlocklistSubscriptionsRequest { + return new GetBlocklistSubscriptionsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetBlocklistSubscriptionsRequest { + return new GetBlocklistSubscriptionsRequest().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetBlocklistSubscriptionsRequest + | PlainMessage + | undefined, + b: + | GetBlocklistSubscriptionsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetBlocklistSubscriptionsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetBlocklistSubscriptionsResponse + */ +export class GetBlocklistSubscriptionsResponse extends Message { + /** + * @generated from field: repeated string list_uris = 1; + */ + listUris: string[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetBlocklistSubscriptionsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'list_uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetBlocklistSubscriptionsResponse { + return new GetBlocklistSubscriptionsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetBlocklistSubscriptionsResponse { + return new GetBlocklistSubscriptionsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetBlocklistSubscriptionsResponse { + return new GetBlocklistSubscriptionsResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetBlocklistSubscriptionsResponse + | PlainMessage + | undefined, + b: + | GetBlocklistSubscriptionsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetBlocklistSubscriptionsResponse, a, b) + } +} + +/** + * - list recent notifications for a user + * - notifications should include a uri for the record that caused the notif & a “reason” for the notification (reply, like, quotepost, etc) + * - this should include both read & unread notifs + * + * @generated from message bsky.GetNotificationsRequest + */ +export class GetNotificationsRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetNotificationsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetNotificationsRequest { + return new GetNotificationsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetNotificationsRequest { + return new GetNotificationsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetNotificationsRequest { + return new GetNotificationsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetNotificationsRequest + | PlainMessage + | undefined, + b: + | GetNotificationsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetNotificationsRequest, a, b) + } +} + +/** + * @generated from message bsky.Notification + */ +export class Notification extends Message { + /** + * @generated from field: string recipient_did = 1; + */ + recipientDid = '' + + /** + * @generated from field: string uri = 2; + */ + uri = '' + + /** + * @generated from field: string reason = 3; + */ + reason = '' + + /** + * @generated from field: string reason_subject = 4; + */ + reasonSubject = '' + + /** + * @generated from field: google.protobuf.Timestamp timestamp = 5; + */ + timestamp?: Timestamp + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.Notification' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'recipient_did', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + { no: 2, name: 'uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 3, name: 'reason', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { + no: 4, + name: 'reason_subject', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + { no: 5, name: 'timestamp', kind: 'message', T: Timestamp }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): Notification { + return new Notification().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): Notification { + return new Notification().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): Notification { + return new Notification().fromJsonString(jsonString, options) + } + + static equals( + a: Notification | PlainMessage | undefined, + b: Notification | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(Notification, a, b) + } +} + +/** + * @generated from message bsky.GetNotificationsResponse + */ +export class GetNotificationsResponse extends Message { + /** + * @generated from field: repeated bsky.Notification notifications = 1; + */ + notifications: Notification[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetNotificationsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'notifications', + kind: 'message', + T: Notification, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetNotificationsResponse { + return new GetNotificationsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetNotificationsResponse { + return new GetNotificationsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetNotificationsResponse { + return new GetNotificationsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetNotificationsResponse + | PlainMessage + | undefined, + b: + | GetNotificationsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetNotificationsResponse, a, b) + } +} + +/** + * - update a user’s “last seen time” + * - `updateSeen` + * + * @generated from message bsky.UpdateNotificationSeenRequest + */ +export class UpdateNotificationSeenRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: google.protobuf.Timestamp timestamp = 2; + */ + timestamp?: Timestamp + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.UpdateNotificationSeenRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'timestamp', kind: 'message', T: Timestamp }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): UpdateNotificationSeenRequest { + return new UpdateNotificationSeenRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): UpdateNotificationSeenRequest { + return new UpdateNotificationSeenRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): UpdateNotificationSeenRequest { + return new UpdateNotificationSeenRequest().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | UpdateNotificationSeenRequest + | PlainMessage + | undefined, + b: + | UpdateNotificationSeenRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(UpdateNotificationSeenRequest, a, b) + } +} + +/** + * @generated from message bsky.UpdateNotificationSeenResponse + */ +export class UpdateNotificationSeenResponse extends Message { + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.UpdateNotificationSeenResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => []) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): UpdateNotificationSeenResponse { + return new UpdateNotificationSeenResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): UpdateNotificationSeenResponse { + return new UpdateNotificationSeenResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): UpdateNotificationSeenResponse { + return new UpdateNotificationSeenResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | UpdateNotificationSeenResponse + | PlainMessage + | undefined, + b: + | UpdateNotificationSeenResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(UpdateNotificationSeenResponse, a, b) + } +} + +/** + * - get a user’s “last seen time” + * - hydrating read state onto notifications + * + * @generated from message bsky.GetNotificationSeenRequest + */ +export class GetNotificationSeenRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetNotificationSeenRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetNotificationSeenRequest { + return new GetNotificationSeenRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetNotificationSeenRequest { + return new GetNotificationSeenRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetNotificationSeenRequest { + return new GetNotificationSeenRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetNotificationSeenRequest + | PlainMessage + | undefined, + b: + | GetNotificationSeenRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetNotificationSeenRequest, a, b) + } +} + +/** + * @generated from message bsky.GetNotificationSeenResponse + */ +export class GetNotificationSeenResponse extends Message { + /** + * @generated from field: google.protobuf.Timestamp timestamp = 1; + */ + timestamp?: Timestamp + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetNotificationSeenResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'timestamp', kind: 'message', T: Timestamp }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetNotificationSeenResponse { + return new GetNotificationSeenResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetNotificationSeenResponse { + return new GetNotificationSeenResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetNotificationSeenResponse { + return new GetNotificationSeenResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetNotificationSeenResponse + | PlainMessage + | undefined, + b: + | GetNotificationSeenResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetNotificationSeenResponse, a, b) + } +} + +/** + * - get a count of all unread notifications (notifications after `updateSeen`) + * - `getUnreadCount` + * + * @generated from message bsky.GetUnreadNotificationCountRequest + */ +export class GetUnreadNotificationCountRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetUnreadNotificationCountRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetUnreadNotificationCountRequest { + return new GetUnreadNotificationCountRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetUnreadNotificationCountRequest { + return new GetUnreadNotificationCountRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetUnreadNotificationCountRequest { + return new GetUnreadNotificationCountRequest().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetUnreadNotificationCountRequest + | PlainMessage + | undefined, + b: + | GetUnreadNotificationCountRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetUnreadNotificationCountRequest, a, b) + } +} + +/** + * @generated from message bsky.GetUnreadNotificationCountResponse + */ +export class GetUnreadNotificationCountResponse extends Message { + /** + * @generated from field: int32 count = 1; + */ + count = 0 + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetUnreadNotificationCountResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'count', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetUnreadNotificationCountResponse { + return new GetUnreadNotificationCountResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetUnreadNotificationCountResponse { + return new GetUnreadNotificationCountResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetUnreadNotificationCountResponse { + return new GetUnreadNotificationCountResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetUnreadNotificationCountResponse + | PlainMessage + | undefined, + b: + | GetUnreadNotificationCountResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetUnreadNotificationCountResponse, a, b) + } +} + +/** + * - Return uris of feed generator records created by user A + * - `getActorFeeds` + * + * @generated from message bsky.GetActorFeedsRequest + */ +export class GetActorFeedsRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetActorFeedsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetActorFeedsRequest { + return new GetActorFeedsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetActorFeedsRequest { + return new GetActorFeedsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetActorFeedsRequest { + return new GetActorFeedsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetActorFeedsRequest | PlainMessage | undefined, + b: GetActorFeedsRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetActorFeedsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetActorFeedsResponse + */ +export class GetActorFeedsResponse extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetActorFeedsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetActorFeedsResponse { + return new GetActorFeedsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetActorFeedsResponse { + return new GetActorFeedsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetActorFeedsResponse { + return new GetActorFeedsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: GetActorFeedsResponse | PlainMessage | undefined, + b: GetActorFeedsResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetActorFeedsResponse, a, b) + } +} + +/** + * - Returns a list of suggested feed generator uris for an actor, paginated + * - `getSuggestedFeeds` + * - This is currently just hardcoded in the Appview DB + * + * @generated from message bsky.GetSuggestedFeedsRequest + */ +export class GetSuggestedFeedsRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetSuggestedFeedsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetSuggestedFeedsRequest { + return new GetSuggestedFeedsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetSuggestedFeedsRequest { + return new GetSuggestedFeedsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetSuggestedFeedsRequest { + return new GetSuggestedFeedsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetSuggestedFeedsRequest + | PlainMessage + | undefined, + b: + | GetSuggestedFeedsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetSuggestedFeedsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetSuggestedFeedsResponse + */ +export class GetSuggestedFeedsResponse extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetSuggestedFeedsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetSuggestedFeedsResponse { + return new GetSuggestedFeedsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetSuggestedFeedsResponse { + return new GetSuggestedFeedsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetSuggestedFeedsResponse { + return new GetSuggestedFeedsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetSuggestedFeedsResponse + | PlainMessage + | undefined, + b: + | GetSuggestedFeedsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetSuggestedFeedsResponse, a, b) + } +} + +/** + * @generated from message bsky.SearchFeedGeneratorsRequest + */ +export class SearchFeedGeneratorsRequest extends Message { + /** + * @generated from field: string query = 1; + */ + query = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.SearchFeedGeneratorsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'query', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): SearchFeedGeneratorsRequest { + return new SearchFeedGeneratorsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): SearchFeedGeneratorsRequest { + return new SearchFeedGeneratorsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): SearchFeedGeneratorsRequest { + return new SearchFeedGeneratorsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | SearchFeedGeneratorsRequest + | PlainMessage + | undefined, + b: + | SearchFeedGeneratorsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(SearchFeedGeneratorsRequest, a, b) + } +} + +/** + * @generated from message bsky.SearchFeedGeneratorsResponse + */ +export class SearchFeedGeneratorsResponse extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.SearchFeedGeneratorsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): SearchFeedGeneratorsResponse { + return new SearchFeedGeneratorsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): SearchFeedGeneratorsResponse { + return new SearchFeedGeneratorsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): SearchFeedGeneratorsResponse { + return new SearchFeedGeneratorsResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | SearchFeedGeneratorsResponse + | PlainMessage + | undefined, + b: + | SearchFeedGeneratorsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(SearchFeedGeneratorsResponse, a, b) + } +} + +/** + * - Returns feed generator validity and online status with uris A, B, C… + * - Not currently being used, but could be worhthwhile. + * + * @generated from message bsky.GetFeedGeneratorStatusRequest + */ +export class GetFeedGeneratorStatusRequest extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetFeedGeneratorStatusRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetFeedGeneratorStatusRequest { + return new GetFeedGeneratorStatusRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetFeedGeneratorStatusRequest { + return new GetFeedGeneratorStatusRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetFeedGeneratorStatusRequest { + return new GetFeedGeneratorStatusRequest().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetFeedGeneratorStatusRequest + | PlainMessage + | undefined, + b: + | GetFeedGeneratorStatusRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetFeedGeneratorStatusRequest, a, b) + } +} + +/** + * @generated from message bsky.GetFeedGeneratorStatusResponse + */ +export class GetFeedGeneratorStatusResponse extends Message { + /** + * @generated from field: repeated string status = 1; + */ + status: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetFeedGeneratorStatusResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'status', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetFeedGeneratorStatusResponse { + return new GetFeedGeneratorStatusResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetFeedGeneratorStatusResponse { + return new GetFeedGeneratorStatusResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetFeedGeneratorStatusResponse { + return new GetFeedGeneratorStatusResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetFeedGeneratorStatusResponse + | PlainMessage + | undefined, + b: + | GetFeedGeneratorStatusResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetFeedGeneratorStatusResponse, a, b) + } +} + +/** + * - Returns recent posts authored by a given DID, paginated + * - `getAuthorFeed` + * - Optionally: filter by if a post is/isn’t a reply and if a post has a media object in it + * + * @generated from message bsky.GetAuthorFeedRequest + */ +export class GetAuthorFeedRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + /** + * @generated from field: bsky.FeedType feed_type = 4; + */ + feedType = FeedType.UNSPECIFIED + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetAuthorFeedRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 4, name: 'feed_type', kind: 'enum', T: proto3.getEnumType(FeedType) }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetAuthorFeedRequest { + return new GetAuthorFeedRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetAuthorFeedRequest { + return new GetAuthorFeedRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetAuthorFeedRequest { + return new GetAuthorFeedRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetAuthorFeedRequest | PlainMessage | undefined, + b: GetAuthorFeedRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetAuthorFeedRequest, a, b) + } +} + +/** + * @generated from message bsky.AuthorFeedItem + */ +export class AuthorFeedItem extends Message { + /** + * @generated from field: string uri = 1; + */ + uri = '' + + /** + * @generated from field: string cid = 2; + */ + cid = '' + + /** + * @generated from field: string repost = 3; + */ + repost = '' + + /** + * @generated from field: string repost_cid = 4; + */ + repostCid = '' + + /** + * @generated from field: bool posts_and_author_threads = 5; + */ + postsAndAuthorThreads = false + + /** + * @generated from field: bool posts_no_replies = 6; + */ + postsNoReplies = false + + /** + * @generated from field: bool posts_with_media = 7; + */ + postsWithMedia = false + + /** + * @generated from field: bool is_reply = 8; + */ + isReply = false + + /** + * @generated from field: bool is_repost = 9; + */ + isRepost = false + + /** + * @generated from field: bool is_quote_post = 10; + */ + isQuotePost = false + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.AuthorFeedItem' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'cid', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 3, name: 'repost', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 4, name: 'repost_cid', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { + no: 5, + name: 'posts_and_author_threads', + kind: 'scalar', + T: 8 /* ScalarType.BOOL */, + }, + { + no: 6, + name: 'posts_no_replies', + kind: 'scalar', + T: 8 /* ScalarType.BOOL */, + }, + { + no: 7, + name: 'posts_with_media', + kind: 'scalar', + T: 8 /* ScalarType.BOOL */, + }, + { no: 8, name: 'is_reply', kind: 'scalar', T: 8 /* ScalarType.BOOL */ }, + { no: 9, name: 'is_repost', kind: 'scalar', T: 8 /* ScalarType.BOOL */ }, + { + no: 10, + name: 'is_quote_post', + kind: 'scalar', + T: 8 /* ScalarType.BOOL */, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): AuthorFeedItem { + return new AuthorFeedItem().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): AuthorFeedItem { + return new AuthorFeedItem().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): AuthorFeedItem { + return new AuthorFeedItem().fromJsonString(jsonString, options) + } + + static equals( + a: AuthorFeedItem | PlainMessage | undefined, + b: AuthorFeedItem | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(AuthorFeedItem, a, b) + } +} + +/** + * @generated from message bsky.GetAuthorFeedResponse + */ +export class GetAuthorFeedResponse extends Message { + /** + * @generated from field: repeated bsky.AuthorFeedItem items = 1; + */ + items: AuthorFeedItem[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetAuthorFeedResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'items', + kind: 'message', + T: AuthorFeedItem, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetAuthorFeedResponse { + return new GetAuthorFeedResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetAuthorFeedResponse { + return new GetAuthorFeedResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetAuthorFeedResponse { + return new GetAuthorFeedResponse().fromJsonString(jsonString, options) + } + + static equals( + a: GetAuthorFeedResponse | PlainMessage | undefined, + b: GetAuthorFeedResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetAuthorFeedResponse, a, b) + } +} + +/** + * - Returns recent posts authored by users followed by a given DID, paginated + * - `getTimeline` + * + * @generated from message bsky.GetTimelineRequest + */ +export class GetTimelineRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + /** + * @generated from field: bool exclude_replies = 4; + */ + excludeReplies = false + + /** + * @generated from field: bool exclude_reposts = 5; + */ + excludeReposts = false + + /** + * @generated from field: bool exclude_quotes = 6; + */ + excludeQuotes = false + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetTimelineRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { + no: 4, + name: 'exclude_replies', + kind: 'scalar', + T: 8 /* ScalarType.BOOL */, + }, + { + no: 5, + name: 'exclude_reposts', + kind: 'scalar', + T: 8 /* ScalarType.BOOL */, + }, + { + no: 6, + name: 'exclude_quotes', + kind: 'scalar', + T: 8 /* ScalarType.BOOL */, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetTimelineRequest { + return new GetTimelineRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetTimelineRequest { + return new GetTimelineRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetTimelineRequest { + return new GetTimelineRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetTimelineRequest | PlainMessage | undefined, + b: GetTimelineRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetTimelineRequest, a, b) + } +} + +/** + * @generated from message bsky.GetTimelineResponse + */ +export class GetTimelineResponse extends Message { + /** + * @generated from field: repeated bsky.TimelineFeedItem items = 1; + */ + items: TimelineFeedItem[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetTimelineResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'items', + kind: 'message', + T: TimelineFeedItem, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetTimelineResponse { + return new GetTimelineResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetTimelineResponse { + return new GetTimelineResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetTimelineResponse { + return new GetTimelineResponse().fromJsonString(jsonString, options) + } + + static equals( + a: GetTimelineResponse | PlainMessage | undefined, + b: GetTimelineResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetTimelineResponse, a, b) + } +} + +/** + * @generated from message bsky.TimelineFeedItem + */ +export class TimelineFeedItem extends Message { + /** + * @generated from field: string uri = 1; + */ + uri = '' + + /** + * @generated from field: string cid = 2; + */ + cid = '' + + /** + * @generated from field: string repost = 3; + */ + repost = '' + + /** + * @generated from field: string repost_cid = 4; + */ + repostCid = '' + + /** + * @generated from field: bool is_reply = 5; + */ + isReply = false + + /** + * @generated from field: bool is_repost = 6; + */ + isRepost = false + + /** + * @generated from field: bool is_quote_post = 7; + */ + isQuotePost = false + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.TimelineFeedItem' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'cid', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 3, name: 'repost', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 4, name: 'repost_cid', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 5, name: 'is_reply', kind: 'scalar', T: 8 /* ScalarType.BOOL */ }, + { no: 6, name: 'is_repost', kind: 'scalar', T: 8 /* ScalarType.BOOL */ }, + { + no: 7, + name: 'is_quote_post', + kind: 'scalar', + T: 8 /* ScalarType.BOOL */, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): TimelineFeedItem { + return new TimelineFeedItem().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): TimelineFeedItem { + return new TimelineFeedItem().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): TimelineFeedItem { + return new TimelineFeedItem().fromJsonString(jsonString, options) + } + + static equals( + a: TimelineFeedItem | PlainMessage | undefined, + b: TimelineFeedItem | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(TimelineFeedItem, a, b) + } +} + +/** + * - Return recent post uris from users in list A + * - `getListFeed` + * - (This is essentially the same as `getTimeline` but instead of follows of a did, it is list items of a list) + * + * @generated from message bsky.GetListFeedRequest + */ +export class GetListFeedRequest extends Message { + /** + * @generated from field: string list_uri = 1; + */ + listUri = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + /** + * @generated from field: bool exclude_replies = 4; + */ + excludeReplies = false + + /** + * @generated from field: bool exclude_reposts = 5; + */ + excludeReposts = false + + /** + * @generated from field: bool exclude_quotes = 6; + */ + excludeQuotes = false + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetListFeedRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'list_uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { + no: 4, + name: 'exclude_replies', + kind: 'scalar', + T: 8 /* ScalarType.BOOL */, + }, + { + no: 5, + name: 'exclude_reposts', + kind: 'scalar', + T: 8 /* ScalarType.BOOL */, + }, + { + no: 6, + name: 'exclude_quotes', + kind: 'scalar', + T: 8 /* ScalarType.BOOL */, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetListFeedRequest { + return new GetListFeedRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetListFeedRequest { + return new GetListFeedRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetListFeedRequest { + return new GetListFeedRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetListFeedRequest | PlainMessage | undefined, + b: GetListFeedRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetListFeedRequest, a, b) + } +} + +/** + * @generated from message bsky.GetListFeedResponse + */ +export class GetListFeedResponse extends Message { + /** + * @generated from field: repeated bsky.TimelineFeedItem items = 1; + */ + items: TimelineFeedItem[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetListFeedResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'items', + kind: 'message', + T: TimelineFeedItem, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetListFeedResponse { + return new GetListFeedResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetListFeedResponse { + return new GetListFeedResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetListFeedResponse { + return new GetListFeedResponse().fromJsonString(jsonString, options) + } + + static equals( + a: GetListFeedResponse | PlainMessage | undefined, + b: GetListFeedResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetListFeedResponse, a, b) + } +} + +/** + * Return posts uris of any replies N levels above or M levels below post A + * + * @generated from message bsky.GetThreadRequest + */ +export class GetThreadRequest extends Message { + /** + * @generated from field: string post_uri = 1; + */ + postUri = '' + + /** + * @generated from field: int32 above = 2; + */ + above = 0 + + /** + * @generated from field: int32 below = 3; + */ + below = 0 + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetThreadRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'post_uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'above', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'below', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetThreadRequest { + return new GetThreadRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetThreadRequest { + return new GetThreadRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetThreadRequest { + return new GetThreadRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetThreadRequest | PlainMessage | undefined, + b: GetThreadRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetThreadRequest, a, b) + } +} + +/** + * @generated from message bsky.GetThreadResponse + */ +export class GetThreadResponse extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetThreadResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetThreadResponse { + return new GetThreadResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetThreadResponse { + return new GetThreadResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetThreadResponse { + return new GetThreadResponse().fromJsonString(jsonString, options) + } + + static equals( + a: GetThreadResponse | PlainMessage | undefined, + b: GetThreadResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetThreadResponse, a, b) + } +} + +/** + * - Return DIDs of actors matching term, paginated + * - `searchActors` skeleton + * + * @generated from message bsky.SearchActorsRequest + */ +export class SearchActorsRequest extends Message { + /** + * @generated from field: string term = 1; + */ + term = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.SearchActorsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'term', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): SearchActorsRequest { + return new SearchActorsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): SearchActorsRequest { + return new SearchActorsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): SearchActorsRequest { + return new SearchActorsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: SearchActorsRequest | PlainMessage | undefined, + b: SearchActorsRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(SearchActorsRequest, a, b) + } +} + +/** + * @generated from message bsky.SearchActorsResponse + */ +export class SearchActorsResponse extends Message { + /** + * @generated from field: repeated string dids = 1; + */ + dids: string[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.SearchActorsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'dids', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): SearchActorsResponse { + return new SearchActorsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): SearchActorsResponse { + return new SearchActorsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): SearchActorsResponse { + return new SearchActorsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: SearchActorsResponse | PlainMessage | undefined, + b: SearchActorsResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(SearchActorsResponse, a, b) + } +} + +/** + * - Return uris of posts matching term, paginated + * - `searchPosts` skeleton + * + * @generated from message bsky.SearchPostsRequest + */ +export class SearchPostsRequest extends Message { + /** + * @generated from field: string term = 1; + */ + term = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.SearchPostsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'term', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): SearchPostsRequest { + return new SearchPostsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): SearchPostsRequest { + return new SearchPostsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): SearchPostsRequest { + return new SearchPostsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: SearchPostsRequest | PlainMessage | undefined, + b: SearchPostsRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(SearchPostsRequest, a, b) + } +} + +/** + * @generated from message bsky.SearchPostsResponse + */ +export class SearchPostsResponse extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.SearchPostsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): SearchPostsResponse { + return new SearchPostsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): SearchPostsResponse { + return new SearchPostsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): SearchPostsResponse { + return new SearchPostsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: SearchPostsResponse | PlainMessage | undefined, + b: SearchPostsResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(SearchPostsResponse, a, b) + } +} + +/** + * - Return DIDs of suggested follows for a user, excluding anyone they already follow + * - `getSuggestions`, `getSuggestedFollowsByActor` + * + * @generated from message bsky.GetFollowSuggestionsRequest + */ +export class GetFollowSuggestionsRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: string relative_to_did = 2; + */ + relativeToDid = '' + + /** + * @generated from field: int32 limit = 3; + */ + limit = 0 + + /** + * @generated from field: string cursor = 4; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetFollowSuggestionsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { + no: 2, + name: 'relative_to_did', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + { no: 3, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 4, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetFollowSuggestionsRequest { + return new GetFollowSuggestionsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetFollowSuggestionsRequest { + return new GetFollowSuggestionsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetFollowSuggestionsRequest { + return new GetFollowSuggestionsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetFollowSuggestionsRequest + | PlainMessage + | undefined, + b: + | GetFollowSuggestionsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetFollowSuggestionsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetFollowSuggestionsResponse + */ +export class GetFollowSuggestionsResponse extends Message { + /** + * @generated from field: repeated string dids = 1; + */ + dids: string[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetFollowSuggestionsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'dids', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetFollowSuggestionsResponse { + return new GetFollowSuggestionsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetFollowSuggestionsResponse { + return new GetFollowSuggestionsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetFollowSuggestionsResponse { + return new GetFollowSuggestionsResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetFollowSuggestionsResponse + | PlainMessage + | undefined, + b: + | GetFollowSuggestionsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetFollowSuggestionsResponse, a, b) + } +} + +/** + * @generated from message bsky.SuggestedEntity + */ +export class SuggestedEntity extends Message { + /** + * @generated from field: string tag = 1; + */ + tag = '' + + /** + * @generated from field: string subject = 2; + */ + subject = '' + + /** + * @generated from field: string subject_type = 3; + */ + subjectType = '' + + /** + * @generated from field: int64 priority = 4; + */ + priority = protoInt64.zero + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.SuggestedEntity' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'tag', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'subject', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { + no: 3, + name: 'subject_type', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + { no: 4, name: 'priority', kind: 'scalar', T: 3 /* ScalarType.INT64 */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): SuggestedEntity { + return new SuggestedEntity().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): SuggestedEntity { + return new SuggestedEntity().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): SuggestedEntity { + return new SuggestedEntity().fromJsonString(jsonString, options) + } + + static equals( + a: SuggestedEntity | PlainMessage | undefined, + b: SuggestedEntity | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(SuggestedEntity, a, b) + } +} + +/** + * @generated from message bsky.GetSuggestedEntitiesRequest + */ +export class GetSuggestedEntitiesRequest extends Message { + /** + * @generated from field: int32 limit = 1; + */ + limit = 0 + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetSuggestedEntitiesRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetSuggestedEntitiesRequest { + return new GetSuggestedEntitiesRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetSuggestedEntitiesRequest { + return new GetSuggestedEntitiesRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetSuggestedEntitiesRequest { + return new GetSuggestedEntitiesRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetSuggestedEntitiesRequest + | PlainMessage + | undefined, + b: + | GetSuggestedEntitiesRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetSuggestedEntitiesRequest, a, b) + } +} + +/** + * @generated from message bsky.GetSuggestedEntitiesResponse + */ +export class GetSuggestedEntitiesResponse extends Message { + /** + * @generated from field: repeated bsky.SuggestedEntity entities = 1; + */ + entities: SuggestedEntity[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetSuggestedEntitiesResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'entities', + kind: 'message', + T: SuggestedEntity, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetSuggestedEntitiesResponse { + return new GetSuggestedEntitiesResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetSuggestedEntitiesResponse { + return new GetSuggestedEntitiesResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetSuggestedEntitiesResponse { + return new GetSuggestedEntitiesResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | GetSuggestedEntitiesResponse + | PlainMessage + | undefined, + b: + | GetSuggestedEntitiesResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetSuggestedEntitiesResponse, a, b) + } +} + +/** + * - Return post reply count with uris A, B, C… + * - All feed hydration + * + * @generated from message bsky.GetPostReplyCountsRequest + */ +export class GetPostReplyCountsRequest extends Message { + /** + * @generated from field: repeated bsky.RecordRef refs = 1; + */ + refs: RecordRef[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetPostReplyCountsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'refs', kind: 'message', T: RecordRef, repeated: true }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetPostReplyCountsRequest { + return new GetPostReplyCountsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetPostReplyCountsRequest { + return new GetPostReplyCountsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetPostReplyCountsRequest { + return new GetPostReplyCountsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetPostReplyCountsRequest + | PlainMessage + | undefined, + b: + | GetPostReplyCountsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetPostReplyCountsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetPostReplyCountsResponse + */ +export class GetPostReplyCountsResponse extends Message { + /** + * @generated from field: repeated int32 counts = 1; + */ + counts: number[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetPostReplyCountsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'counts', + kind: 'scalar', + T: 5 /* ScalarType.INT32 */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetPostReplyCountsResponse { + return new GetPostReplyCountsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetPostReplyCountsResponse { + return new GetPostReplyCountsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetPostReplyCountsResponse { + return new GetPostReplyCountsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetPostReplyCountsResponse + | PlainMessage + | undefined, + b: + | GetPostReplyCountsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetPostReplyCountsResponse, a, b) + } +} + +/** + * - Get all labels on a subjects A, B, C (uri or did) issued by dids D, E, F… + * - label hydration on nearly every view + * + * @generated from message bsky.GetLabelsRequest + */ +export class GetLabelsRequest extends Message { + /** + * @generated from field: repeated string subjects = 1; + */ + subjects: string[] = [] + + /** + * @generated from field: repeated string issuers = 2; + */ + issuers: string[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetLabelsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'subjects', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + { + no: 2, + name: 'issuers', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetLabelsRequest { + return new GetLabelsRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetLabelsRequest { + return new GetLabelsRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetLabelsRequest { + return new GetLabelsRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetLabelsRequest | PlainMessage | undefined, + b: GetLabelsRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetLabelsRequest, a, b) + } +} + +/** + * @generated from message bsky.GetLabelsResponse + */ +export class GetLabelsResponse extends Message { + /** + * @generated from field: repeated bytes labels = 1; + */ + labels: Uint8Array[] = [] + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetLabelsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'labels', + kind: 'scalar', + T: 12 /* ScalarType.BYTES */, + repeated: true, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetLabelsResponse { + return new GetLabelsResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetLabelsResponse { + return new GetLabelsResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetLabelsResponse { + return new GetLabelsResponse().fromJsonString(jsonString, options) + } + + static equals( + a: GetLabelsResponse | PlainMessage | undefined, + b: GetLabelsResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetLabelsResponse, a, b) + } +} + +/** + * - Latest repo rev of user w/ DID + * - Read-after-write header in`getProfile`, `getProfiles`, `getActorLikes`, `getAuthorFeed`, `getListFeed`, `getPostThread`, `getTimeline`. Could it be view dependent? + * + * @generated from message bsky.GetLatestRevRequest + */ +export class GetLatestRevRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetLatestRevRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetLatestRevRequest { + return new GetLatestRevRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetLatestRevRequest { + return new GetLatestRevRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetLatestRevRequest { + return new GetLatestRevRequest().fromJsonString(jsonString, options) + } + + static equals( + a: GetLatestRevRequest | PlainMessage | undefined, + b: GetLatestRevRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetLatestRevRequest, a, b) + } +} + +/** + * @generated from message bsky.GetLatestRevResponse + */ +export class GetLatestRevResponse extends Message { + /** + * @generated from field: string rev = 1; + */ + rev = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetLatestRevResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'rev', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetLatestRevResponse { + return new GetLatestRevResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetLatestRevResponse { + return new GetLatestRevResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetLatestRevResponse { + return new GetLatestRevResponse().fromJsonString(jsonString, options) + } + + static equals( + a: GetLatestRevResponse | PlainMessage | undefined, + b: GetLatestRevResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(GetLatestRevResponse, a, b) + } +} + +/** + * @generated from message bsky.GetIdentityByDidRequest + */ +export class GetIdentityByDidRequest extends Message { + /** + * @generated from field: string did = 1; + */ + did = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetIdentityByDidRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetIdentityByDidRequest { + return new GetIdentityByDidRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetIdentityByDidRequest { + return new GetIdentityByDidRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetIdentityByDidRequest { + return new GetIdentityByDidRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetIdentityByDidRequest + | PlainMessage + | undefined, + b: + | GetIdentityByDidRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetIdentityByDidRequest, a, b) + } +} + +/** + * @generated from message bsky.GetIdentityByDidResponse + */ +export class GetIdentityByDidResponse extends Message { + /** + * @generated from field: string did = 1; + */ + did = '' + + /** + * @generated from field: string handle = 2; + */ + handle = '' + + /** + * @generated from field: bytes keys = 3; + */ + keys = new Uint8Array(0) + + /** + * @generated from field: bytes services = 4; + */ + services = new Uint8Array(0) + + /** + * @generated from field: google.protobuf.Timestamp updated = 5; + */ + updated?: Timestamp + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetIdentityByDidResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'handle', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 3, name: 'keys', kind: 'scalar', T: 12 /* ScalarType.BYTES */ }, + { no: 4, name: 'services', kind: 'scalar', T: 12 /* ScalarType.BYTES */ }, + { no: 5, name: 'updated', kind: 'message', T: Timestamp }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetIdentityByDidResponse { + return new GetIdentityByDidResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetIdentityByDidResponse { + return new GetIdentityByDidResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetIdentityByDidResponse { + return new GetIdentityByDidResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetIdentityByDidResponse + | PlainMessage + | undefined, + b: + | GetIdentityByDidResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetIdentityByDidResponse, a, b) + } +} + +/** + * @generated from message bsky.GetIdentityByHandleRequest + */ +export class GetIdentityByHandleRequest extends Message { + /** + * @generated from field: string handle = 1; + */ + handle = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetIdentityByHandleRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'handle', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetIdentityByHandleRequest { + return new GetIdentityByHandleRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetIdentityByHandleRequest { + return new GetIdentityByHandleRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetIdentityByHandleRequest { + return new GetIdentityByHandleRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetIdentityByHandleRequest + | PlainMessage + | undefined, + b: + | GetIdentityByHandleRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetIdentityByHandleRequest, a, b) + } +} + +/** + * @generated from message bsky.GetIdentityByHandleResponse + */ +export class GetIdentityByHandleResponse extends Message { + /** + * @generated from field: string handle = 1; + */ + handle = '' + + /** + * @generated from field: string did = 2; + */ + did = '' + + /** + * @generated from field: bytes keys = 3; + */ + keys = new Uint8Array(0) + + /** + * @generated from field: bytes services = 4; + */ + services = new Uint8Array(0) + + /** + * @generated from field: google.protobuf.Timestamp updated = 5; + */ + updated?: Timestamp + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetIdentityByHandleResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'handle', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 3, name: 'keys', kind: 'scalar', T: 12 /* ScalarType.BYTES */ }, + { no: 4, name: 'services', kind: 'scalar', T: 12 /* ScalarType.BYTES */ }, + { no: 5, name: 'updated', kind: 'message', T: Timestamp }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetIdentityByHandleResponse { + return new GetIdentityByHandleResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetIdentityByHandleResponse { + return new GetIdentityByHandleResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetIdentityByHandleResponse { + return new GetIdentityByHandleResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetIdentityByHandleResponse + | PlainMessage + | undefined, + b: + | GetIdentityByHandleResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetIdentityByHandleResponse, a, b) + } +} + +/** + * @generated from message bsky.GetBlobTakedownRequest + */ +export class GetBlobTakedownRequest extends Message { + /** + * @generated from field: string did = 1; + */ + did = '' + + /** + * @generated from field: string cid = 2; + */ + cid = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetBlobTakedownRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'cid', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetBlobTakedownRequest { + return new GetBlobTakedownRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetBlobTakedownRequest { + return new GetBlobTakedownRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetBlobTakedownRequest { + return new GetBlobTakedownRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetBlobTakedownRequest + | PlainMessage + | undefined, + b: + | GetBlobTakedownRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetBlobTakedownRequest, a, b) + } +} + +/** + * @generated from message bsky.GetBlobTakedownResponse + */ +export class GetBlobTakedownResponse extends Message { + /** + * @generated from field: bool taken_down = 1; + */ + takenDown = false + + /** + * @generated from field: string takedown_ref = 2; + */ + takedownRef = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetBlobTakedownResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'taken_down', kind: 'scalar', T: 8 /* ScalarType.BOOL */ }, + { + no: 2, + name: 'takedown_ref', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetBlobTakedownResponse { + return new GetBlobTakedownResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetBlobTakedownResponse { + return new GetBlobTakedownResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetBlobTakedownResponse { + return new GetBlobTakedownResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetBlobTakedownResponse + | PlainMessage + | undefined, + b: + | GetBlobTakedownResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetBlobTakedownResponse, a, b) + } +} + +/** + * @generated from message bsky.GetActorTakedownRequest + */ +export class GetActorTakedownRequest extends Message { + /** + * @generated from field: string did = 1; + */ + did = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetActorTakedownRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetActorTakedownRequest { + return new GetActorTakedownRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetActorTakedownRequest { + return new GetActorTakedownRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetActorTakedownRequest { + return new GetActorTakedownRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetActorTakedownRequest + | PlainMessage + | undefined, + b: + | GetActorTakedownRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetActorTakedownRequest, a, b) + } +} + +/** + * @generated from message bsky.GetActorTakedownResponse + */ +export class GetActorTakedownResponse extends Message { + /** + * @generated from field: bool taken_down = 1; + */ + takenDown = false + + /** + * @generated from field: string takedown_ref = 2; + */ + takedownRef = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetActorTakedownResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'taken_down', kind: 'scalar', T: 8 /* ScalarType.BOOL */ }, + { + no: 2, + name: 'takedown_ref', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetActorTakedownResponse { + return new GetActorTakedownResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetActorTakedownResponse { + return new GetActorTakedownResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetActorTakedownResponse { + return new GetActorTakedownResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetActorTakedownResponse + | PlainMessage + | undefined, + b: + | GetActorTakedownResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetActorTakedownResponse, a, b) + } +} + +/** + * @generated from message bsky.GetRecordTakedownRequest + */ +export class GetRecordTakedownRequest extends Message { + /** + * @generated from field: string record_uri = 1; + */ + recordUri = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetRecordTakedownRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'record_uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetRecordTakedownRequest { + return new GetRecordTakedownRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetRecordTakedownRequest { + return new GetRecordTakedownRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetRecordTakedownRequest { + return new GetRecordTakedownRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetRecordTakedownRequest + | PlainMessage + | undefined, + b: + | GetRecordTakedownRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetRecordTakedownRequest, a, b) + } +} + +/** + * @generated from message bsky.GetRecordTakedownResponse + */ +export class GetRecordTakedownResponse extends Message { + /** + * @generated from field: bool taken_down = 1; + */ + takenDown = false + + /** + * @generated from field: string takedown_ref = 2; + */ + takedownRef = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.GetRecordTakedownResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'taken_down', kind: 'scalar', T: 8 /* ScalarType.BOOL */ }, + { + no: 2, + name: 'takedown_ref', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): GetRecordTakedownResponse { + return new GetRecordTakedownResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): GetRecordTakedownResponse { + return new GetRecordTakedownResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): GetRecordTakedownResponse { + return new GetRecordTakedownResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | GetRecordTakedownResponse + | PlainMessage + | undefined, + b: + | GetRecordTakedownResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(GetRecordTakedownResponse, a, b) + } +} + +/** + * Ping + * + * @generated from message bsky.PingRequest + */ +export class PingRequest extends Message { + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.PingRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => []) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): PingRequest { + return new PingRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): PingRequest { + return new PingRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): PingRequest { + return new PingRequest().fromJsonString(jsonString, options) + } + + static equals( + a: PingRequest | PlainMessage | undefined, + b: PingRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(PingRequest, a, b) + } +} + +/** + * @generated from message bsky.PingResponse + */ +export class PingResponse extends Message { + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.PingResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => []) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): PingResponse { + return new PingResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): PingResponse { + return new PingResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): PingResponse { + return new PingResponse().fromJsonString(jsonString, options) + } + + static equals( + a: PingResponse | PlainMessage | undefined, + b: PingResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(PingResponse, a, b) + } +} + +/** + * @generated from message bsky.TakedownActorRequest + */ +export class TakedownActorRequest extends Message { + /** + * @generated from field: string did = 1; + */ + did = '' + + /** + * @generated from field: string ref = 2; + */ + ref = '' + + /** + * @generated from field: google.protobuf.Timestamp seen = 3; + */ + seen?: Timestamp + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.TakedownActorRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'ref', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 3, name: 'seen', kind: 'message', T: Timestamp }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): TakedownActorRequest { + return new TakedownActorRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): TakedownActorRequest { + return new TakedownActorRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): TakedownActorRequest { + return new TakedownActorRequest().fromJsonString(jsonString, options) + } + + static equals( + a: TakedownActorRequest | PlainMessage | undefined, + b: TakedownActorRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(TakedownActorRequest, a, b) + } +} + +/** + * @generated from message bsky.TakedownActorResponse + */ +export class TakedownActorResponse extends Message { + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.TakedownActorResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => []) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): TakedownActorResponse { + return new TakedownActorResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): TakedownActorResponse { + return new TakedownActorResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): TakedownActorResponse { + return new TakedownActorResponse().fromJsonString(jsonString, options) + } + + static equals( + a: TakedownActorResponse | PlainMessage | undefined, + b: TakedownActorResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(TakedownActorResponse, a, b) + } +} + +/** + * @generated from message bsky.UntakedownActorRequest + */ +export class UntakedownActorRequest extends Message { + /** + * @generated from field: string did = 1; + */ + did = '' + + /** + * @generated from field: google.protobuf.Timestamp seen = 2; + */ + seen?: Timestamp + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.UntakedownActorRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'seen', kind: 'message', T: Timestamp }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): UntakedownActorRequest { + return new UntakedownActorRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): UntakedownActorRequest { + return new UntakedownActorRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): UntakedownActorRequest { + return new UntakedownActorRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | UntakedownActorRequest + | PlainMessage + | undefined, + b: + | UntakedownActorRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(UntakedownActorRequest, a, b) + } +} + +/** + * @generated from message bsky.UntakedownActorResponse + */ +export class UntakedownActorResponse extends Message { + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.UntakedownActorResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => []) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): UntakedownActorResponse { + return new UntakedownActorResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): UntakedownActorResponse { + return new UntakedownActorResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): UntakedownActorResponse { + return new UntakedownActorResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | UntakedownActorResponse + | PlainMessage + | undefined, + b: + | UntakedownActorResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(UntakedownActorResponse, a, b) + } +} + +/** + * @generated from message bsky.TakedownBlobRequest + */ +export class TakedownBlobRequest extends Message { + /** + * @generated from field: string did = 1; + */ + did = '' + + /** + * @generated from field: string cid = 2; + */ + cid = '' + + /** + * @generated from field: string ref = 3; + */ + ref = '' + + /** + * @generated from field: google.protobuf.Timestamp seen = 4; + */ + seen?: Timestamp + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.TakedownBlobRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'cid', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 3, name: 'ref', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 4, name: 'seen', kind: 'message', T: Timestamp }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): TakedownBlobRequest { + return new TakedownBlobRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): TakedownBlobRequest { + return new TakedownBlobRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): TakedownBlobRequest { + return new TakedownBlobRequest().fromJsonString(jsonString, options) + } + + static equals( + a: TakedownBlobRequest | PlainMessage | undefined, + b: TakedownBlobRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(TakedownBlobRequest, a, b) + } +} + +/** + * @generated from message bsky.TakedownBlobResponse + */ +export class TakedownBlobResponse extends Message { + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.TakedownBlobResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => []) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): TakedownBlobResponse { + return new TakedownBlobResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): TakedownBlobResponse { + return new TakedownBlobResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): TakedownBlobResponse { + return new TakedownBlobResponse().fromJsonString(jsonString, options) + } + + static equals( + a: TakedownBlobResponse | PlainMessage | undefined, + b: TakedownBlobResponse | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(TakedownBlobResponse, a, b) + } +} + +/** + * @generated from message bsky.UntakedownBlobRequest + */ +export class UntakedownBlobRequest extends Message { + /** + * @generated from field: string did = 1; + */ + did = '' + + /** + * @generated from field: string cid = 2; + */ + cid = '' + + /** + * @generated from field: google.protobuf.Timestamp seen = 3; + */ + seen?: Timestamp + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.UntakedownBlobRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'cid', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 3, name: 'seen', kind: 'message', T: Timestamp }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): UntakedownBlobRequest { + return new UntakedownBlobRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): UntakedownBlobRequest { + return new UntakedownBlobRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): UntakedownBlobRequest { + return new UntakedownBlobRequest().fromJsonString(jsonString, options) + } + + static equals( + a: UntakedownBlobRequest | PlainMessage | undefined, + b: UntakedownBlobRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(UntakedownBlobRequest, a, b) + } +} + +/** + * @generated from message bsky.UntakedownBlobResponse + */ +export class UntakedownBlobResponse extends Message { + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.UntakedownBlobResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => []) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): UntakedownBlobResponse { + return new UntakedownBlobResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): UntakedownBlobResponse { + return new UntakedownBlobResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): UntakedownBlobResponse { + return new UntakedownBlobResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | UntakedownBlobResponse + | PlainMessage + | undefined, + b: + | UntakedownBlobResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(UntakedownBlobResponse, a, b) + } +} + +/** + * @generated from message bsky.TakedownRecordRequest + */ +export class TakedownRecordRequest extends Message { + /** + * @generated from field: string record_uri = 1; + */ + recordUri = '' + + /** + * @generated from field: string ref = 2; + */ + ref = '' + + /** + * @generated from field: google.protobuf.Timestamp seen = 3; + */ + seen?: Timestamp + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.TakedownRecordRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'record_uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'ref', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 3, name: 'seen', kind: 'message', T: Timestamp }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): TakedownRecordRequest { + return new TakedownRecordRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): TakedownRecordRequest { + return new TakedownRecordRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): TakedownRecordRequest { + return new TakedownRecordRequest().fromJsonString(jsonString, options) + } + + static equals( + a: TakedownRecordRequest | PlainMessage | undefined, + b: TakedownRecordRequest | PlainMessage | undefined, + ): boolean { + return proto3.util.equals(TakedownRecordRequest, a, b) + } +} + +/** + * @generated from message bsky.TakedownRecordResponse + */ +export class TakedownRecordResponse extends Message { + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.TakedownRecordResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => []) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): TakedownRecordResponse { + return new TakedownRecordResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): TakedownRecordResponse { + return new TakedownRecordResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): TakedownRecordResponse { + return new TakedownRecordResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | TakedownRecordResponse + | PlainMessage + | undefined, + b: + | TakedownRecordResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(TakedownRecordResponse, a, b) + } +} + +/** + * @generated from message bsky.UntakedownRecordRequest + */ +export class UntakedownRecordRequest extends Message { + /** + * @generated from field: string record_uri = 1; + */ + recordUri = '' + + /** + * @generated from field: google.protobuf.Timestamp seen = 2; + */ + seen?: Timestamp + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.UntakedownRecordRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'record_uri', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'seen', kind: 'message', T: Timestamp }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): UntakedownRecordRequest { + return new UntakedownRecordRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): UntakedownRecordRequest { + return new UntakedownRecordRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): UntakedownRecordRequest { + return new UntakedownRecordRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | UntakedownRecordRequest + | PlainMessage + | undefined, + b: + | UntakedownRecordRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(UntakedownRecordRequest, a, b) + } +} + +/** + * @generated from message bsky.UntakedownRecordResponse + */ +export class UntakedownRecordResponse extends Message { + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.UntakedownRecordResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => []) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): UntakedownRecordResponse { + return new UntakedownRecordResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): UntakedownRecordResponse { + return new UntakedownRecordResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): UntakedownRecordResponse { + return new UntakedownRecordResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | UntakedownRecordResponse + | PlainMessage + | undefined, + b: + | UntakedownRecordResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(UntakedownRecordResponse, a, b) + } +} + +/** + * @generated from message bsky.CreateActorMuteRequest + */ +export class CreateActorMuteRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: string subject_did = 2; + */ + subjectDid = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.CreateActorMuteRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { + no: 2, + name: 'subject_did', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): CreateActorMuteRequest { + return new CreateActorMuteRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): CreateActorMuteRequest { + return new CreateActorMuteRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): CreateActorMuteRequest { + return new CreateActorMuteRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | CreateActorMuteRequest + | PlainMessage + | undefined, + b: + | CreateActorMuteRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(CreateActorMuteRequest, a, b) + } +} + +/** + * @generated from message bsky.CreateActorMuteResponse + */ +export class CreateActorMuteResponse extends Message { + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.CreateActorMuteResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => []) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): CreateActorMuteResponse { + return new CreateActorMuteResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): CreateActorMuteResponse { + return new CreateActorMuteResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): CreateActorMuteResponse { + return new CreateActorMuteResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | CreateActorMuteResponse + | PlainMessage + | undefined, + b: + | CreateActorMuteResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(CreateActorMuteResponse, a, b) + } +} + +/** + * @generated from message bsky.DeleteActorMuteRequest + */ +export class DeleteActorMuteRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: string subject_did = 2; + */ + subjectDid = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.DeleteActorMuteRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { + no: 2, + name: 'subject_did', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): DeleteActorMuteRequest { + return new DeleteActorMuteRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): DeleteActorMuteRequest { + return new DeleteActorMuteRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): DeleteActorMuteRequest { + return new DeleteActorMuteRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | DeleteActorMuteRequest + | PlainMessage + | undefined, + b: + | DeleteActorMuteRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(DeleteActorMuteRequest, a, b) + } +} + +/** + * @generated from message bsky.DeleteActorMuteResponse + */ +export class DeleteActorMuteResponse extends Message { + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.DeleteActorMuteResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => []) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): DeleteActorMuteResponse { + return new DeleteActorMuteResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): DeleteActorMuteResponse { + return new DeleteActorMuteResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): DeleteActorMuteResponse { + return new DeleteActorMuteResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | DeleteActorMuteResponse + | PlainMessage + | undefined, + b: + | DeleteActorMuteResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(DeleteActorMuteResponse, a, b) + } +} + +/** + * @generated from message bsky.ClearActorMutesRequest + */ +export class ClearActorMutesRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.ClearActorMutesRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): ClearActorMutesRequest { + return new ClearActorMutesRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): ClearActorMutesRequest { + return new ClearActorMutesRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): ClearActorMutesRequest { + return new ClearActorMutesRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | ClearActorMutesRequest + | PlainMessage + | undefined, + b: + | ClearActorMutesRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(ClearActorMutesRequest, a, b) + } +} + +/** + * @generated from message bsky.ClearActorMutesResponse + */ +export class ClearActorMutesResponse extends Message { + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.ClearActorMutesResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => []) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): ClearActorMutesResponse { + return new ClearActorMutesResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): ClearActorMutesResponse { + return new ClearActorMutesResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): ClearActorMutesResponse { + return new ClearActorMutesResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | ClearActorMutesResponse + | PlainMessage + | undefined, + b: + | ClearActorMutesResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(ClearActorMutesResponse, a, b) + } +} + +/** + * @generated from message bsky.CreateActorMutelistSubscriptionRequest + */ +export class CreateActorMutelistSubscriptionRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: string subject_uri = 2; + */ + subjectUri = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.CreateActorMutelistSubscriptionRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { + no: 2, + name: 'subject_uri', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): CreateActorMutelistSubscriptionRequest { + return new CreateActorMutelistSubscriptionRequest().fromBinary( + bytes, + options, + ) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): CreateActorMutelistSubscriptionRequest { + return new CreateActorMutelistSubscriptionRequest().fromJson( + jsonValue, + options, + ) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): CreateActorMutelistSubscriptionRequest { + return new CreateActorMutelistSubscriptionRequest().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | CreateActorMutelistSubscriptionRequest + | PlainMessage + | undefined, + b: + | CreateActorMutelistSubscriptionRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(CreateActorMutelistSubscriptionRequest, a, b) + } +} + +/** + * @generated from message bsky.CreateActorMutelistSubscriptionResponse + */ +export class CreateActorMutelistSubscriptionResponse extends Message { + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.CreateActorMutelistSubscriptionResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => []) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): CreateActorMutelistSubscriptionResponse { + return new CreateActorMutelistSubscriptionResponse().fromBinary( + bytes, + options, + ) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): CreateActorMutelistSubscriptionResponse { + return new CreateActorMutelistSubscriptionResponse().fromJson( + jsonValue, + options, + ) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): CreateActorMutelistSubscriptionResponse { + return new CreateActorMutelistSubscriptionResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | CreateActorMutelistSubscriptionResponse + | PlainMessage + | undefined, + b: + | CreateActorMutelistSubscriptionResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(CreateActorMutelistSubscriptionResponse, a, b) + } +} + +/** + * @generated from message bsky.DeleteActorMutelistSubscriptionRequest + */ +export class DeleteActorMutelistSubscriptionRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + /** + * @generated from field: string subject_uri = 2; + */ + subjectUri = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.DeleteActorMutelistSubscriptionRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { + no: 2, + name: 'subject_uri', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): DeleteActorMutelistSubscriptionRequest { + return new DeleteActorMutelistSubscriptionRequest().fromBinary( + bytes, + options, + ) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): DeleteActorMutelistSubscriptionRequest { + return new DeleteActorMutelistSubscriptionRequest().fromJson( + jsonValue, + options, + ) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): DeleteActorMutelistSubscriptionRequest { + return new DeleteActorMutelistSubscriptionRequest().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | DeleteActorMutelistSubscriptionRequest + | PlainMessage + | undefined, + b: + | DeleteActorMutelistSubscriptionRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(DeleteActorMutelistSubscriptionRequest, a, b) + } +} + +/** + * @generated from message bsky.DeleteActorMutelistSubscriptionResponse + */ +export class DeleteActorMutelistSubscriptionResponse extends Message { + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.DeleteActorMutelistSubscriptionResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => []) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): DeleteActorMutelistSubscriptionResponse { + return new DeleteActorMutelistSubscriptionResponse().fromBinary( + bytes, + options, + ) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): DeleteActorMutelistSubscriptionResponse { + return new DeleteActorMutelistSubscriptionResponse().fromJson( + jsonValue, + options, + ) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): DeleteActorMutelistSubscriptionResponse { + return new DeleteActorMutelistSubscriptionResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | DeleteActorMutelistSubscriptionResponse + | PlainMessage + | undefined, + b: + | DeleteActorMutelistSubscriptionResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(DeleteActorMutelistSubscriptionResponse, a, b) + } +} + +/** + * @generated from message bsky.ClearActorMutelistSubscriptionsRequest + */ +export class ClearActorMutelistSubscriptionsRequest extends Message { + /** + * @generated from field: string actor_did = 1; + */ + actorDid = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.ClearActorMutelistSubscriptionsRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'actor_did', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): ClearActorMutelistSubscriptionsRequest { + return new ClearActorMutelistSubscriptionsRequest().fromBinary( + bytes, + options, + ) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): ClearActorMutelistSubscriptionsRequest { + return new ClearActorMutelistSubscriptionsRequest().fromJson( + jsonValue, + options, + ) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): ClearActorMutelistSubscriptionsRequest { + return new ClearActorMutelistSubscriptionsRequest().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | ClearActorMutelistSubscriptionsRequest + | PlainMessage + | undefined, + b: + | ClearActorMutelistSubscriptionsRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(ClearActorMutelistSubscriptionsRequest, a, b) + } +} + +/** + * @generated from message bsky.ClearActorMutelistSubscriptionsResponse + */ +export class ClearActorMutelistSubscriptionsResponse extends Message { + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.ClearActorMutelistSubscriptionsResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => []) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): ClearActorMutelistSubscriptionsResponse { + return new ClearActorMutelistSubscriptionsResponse().fromBinary( + bytes, + options, + ) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): ClearActorMutelistSubscriptionsResponse { + return new ClearActorMutelistSubscriptionsResponse().fromJson( + jsonValue, + options, + ) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): ClearActorMutelistSubscriptionsResponse { + return new ClearActorMutelistSubscriptionsResponse().fromJsonString( + jsonString, + options, + ) + } + + static equals( + a: + | ClearActorMutelistSubscriptionsResponse + | PlainMessage + | undefined, + b: + | ClearActorMutelistSubscriptionsResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(ClearActorMutelistSubscriptionsResponse, a, b) + } +} diff --git a/packages/bsky/src/services/actor/index.ts b/packages/bsky/src/services/actor/index.ts deleted file mode 100644 index 096bf18be9b..00000000000 --- a/packages/bsky/src/services/actor/index.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { sql } from 'kysely' -import { wait } from '@atproto/common' -import { Database } from '../../db' -import { notSoftDeletedClause } from '../../db/util' -import { ActorViews } from './views' -import { ImageUriBuilder } from '../../image/uri' -import { Actor } from '../../db/tables/actor' -import { TimeCidKeyset, paginate } from '../../db/pagination' -import { SearchKeyset, getUserSearchQuery } from '../util/search' -import { FromDb } from '../types' -import { GraphService } from '../graph' -import { LabelService } from '../label' -import { AtUri } from '@atproto/syntax' -import { ids } from '../../lexicon/lexicons' -import { Platform } from '../../notifications' - -export * from './types' - -export class ActorService { - views: ActorViews - - constructor( - public db: Database, - public imgUriBuilder: ImageUriBuilder, - graph: FromDb, - label: FromDb, - ) { - this.views = new ActorViews(this.db, this.imgUriBuilder, graph, label) - } - - static creator( - imgUriBuilder: ImageUriBuilder, - graph: FromDb, - label: FromDb, - ) { - return (db: Database) => new ActorService(db, imgUriBuilder, graph, label) - } - - async getActorDid(handleOrDid: string): Promise { - if (handleOrDid.startsWith('did:')) { - return handleOrDid - } - const subject = await this.getActor(handleOrDid, true) - return subject?.did ?? null - } - - async getActor( - handleOrDid: string, - includeSoftDeleted = false, - ): Promise { - const actors = await this.getActors([handleOrDid], includeSoftDeleted) - return actors[0] || null - } - - async getActors( - handleOrDids: string[], - includeSoftDeleted = false, - ): Promise { - const { ref } = this.db.db.dynamic - const dids: string[] = [] - const handles: string[] = [] - const order: Record = {} - handleOrDids.forEach((item, i) => { - if (item.startsWith('did:')) { - order[item] = i - dids.push(item) - } else { - order[item.toLowerCase()] = i - handles.push(item.toLowerCase()) - } - }) - const results = await this.db.db - .selectFrom('actor') - .if(!includeSoftDeleted, (qb) => - qb.where(notSoftDeletedClause(ref('actor'))), - ) - .where((qb) => { - if (dids.length) { - qb = qb.orWhere('actor.did', 'in', dids) - } - if (handles.length) { - qb = qb.orWhere( - 'actor.handle', - 'in', - handles.length === 1 - ? [handles[0], handles[0]] // a silly (but worthwhile) optimization to avoid usage of actor_handle_tgrm_idx - : handles, - ) - } - return qb - }) - .selectAll() - .execute() - - return results.sort((a, b) => { - const orderA = order[a.did] ?? order[a.handle?.toLowerCase() ?? ''] - const orderB = order[b.did] ?? order[b.handle?.toLowerCase() ?? ''] - return orderA - orderB - }) - } - - async getProfileRecords(dids: string[], includeSoftDeleted = false) { - if (dids.length === 0) return new Map() - const profileUris = dids.map((did) => - AtUri.make(did, ids.AppBskyActorProfile, 'self').toString(), - ) - const { ref } = this.db.db.dynamic - const res = await this.db.db - .selectFrom('record') - .innerJoin('actor', 'actor.did', 'record.did') - .if(!includeSoftDeleted, (qb) => - qb.where(notSoftDeletedClause(ref('actor'))), - ) - .where('uri', 'in', profileUris) - .select(['record.did', 'record.json']) - .execute() - return res.reduce((acc, cur) => { - return acc.set(cur.did, JSON.parse(cur.json)) - }, new Map()) - } - - async getSearchResults({ - cursor, - limit = 25, - query = '', - includeSoftDeleted, - }: { - cursor?: string - limit?: number - query?: string - includeSoftDeleted?: boolean - }) { - const searchField = query.startsWith('did:') ? 'did' : 'handle' - let paginatedBuilder - const { ref } = this.db.db.dynamic - const paginationOptions = { - limit, - cursor, - direction: 'asc' as const, - } - let keyset - - if (query && searchField === 'handle') { - keyset = new SearchKeyset(sql``, sql``) - paginatedBuilder = getUserSearchQuery(this.db, { - query, - includeSoftDeleted, - ...paginationOptions, - }).select('distance') - } else { - paginatedBuilder = this.db.db - .selectFrom('actor') - .select([sql`0`.as('distance')]) - keyset = new ListKeyset(ref('indexedAt'), ref('did')) - - // 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, - ...paginationOptions, - }) - } - - const results: Actor[] = await paginatedBuilder.selectAll('actor').execute() - return { results, cursor: keyset.packFromResult(results) } - } - - async getRepoRev(did: string | null): Promise { - if (did === null) return null - const res = await this.db.db - .selectFrom('actor_sync') - .select('repoRev') - .where('did', '=', did) - .executeTakeFirst() - return res?.repoRev ?? null - } - - async *all( - opts: { - batchSize?: number - forever?: boolean - cooldownMs?: number - startFromDid?: string - } = {}, - ) { - const { - cooldownMs = 1000, - batchSize = 1000, - forever = false, - startFromDid, - } = opts - const baseQuery = this.db.db - .selectFrom('actor') - .selectAll() - .orderBy('did') - .limit(batchSize) - while (true) { - let cursor = startFromDid - do { - const actors = cursor - ? await baseQuery.where('did', '>', cursor).execute() - : await baseQuery.execute() - for (const actor of actors) { - yield actor - } - cursor = actors.at(-1)?.did - } while (cursor) - if (forever) { - await wait(cooldownMs) - } else { - return - } - } - } - - async registerPushDeviceToken( - did: string, - token: string, - platform: Platform, - appId: string, - ) { - await this.db - .asPrimary() - .db.insertInto('notification_push_token') - .values({ did, token, platform, appId }) - .onConflict((oc) => oc.doNothing()) - .execute() - } -} - -type ActorResult = Actor -export class ListKeyset extends TimeCidKeyset<{ - indexedAt: string - did: string // handles are treated identically to cids in TimeCidKeyset -}> { - labelResult(result: { indexedAt: string; did: string }) { - return { primary: result.indexedAt, secondary: result.did } - } -} diff --git a/packages/bsky/src/services/actor/types.ts b/packages/bsky/src/services/actor/types.ts deleted file mode 100644 index d622e641099..00000000000 --- a/packages/bsky/src/services/actor/types.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { ListViewBasic } from '../../lexicon/types/app/bsky/graph/defs' -import { Label } from '../../lexicon/types/com/atproto/label/defs' -import { BlockAndMuteState } from '../graph' -import { ListInfoMap } from '../graph/types' -import { Labels } from '../label' - -export type ActorInfo = { - did: string - handle: string - displayName?: string - description?: string // omitted from basic profile view - avatar?: string - indexedAt?: string // omitted from basic profile view - viewer?: { - muted?: boolean - mutedByList?: ListViewBasic - blockedBy?: boolean - blocking?: string - blockingByList?: ListViewBasic - following?: string - followedBy?: string - } - labels?: Label[] -} -export type ActorInfoMap = { [did: string]: ActorInfo } - -export type ProfileViewMap = ActorInfoMap - -export type ProfileInfo = { - did: string - handle: string | null - profileUri: string | null - profileCid: string | null - displayName: string | null - description: string | null - avatarCid: string | null - indexedAt: string | null - profileJson: string | null - viewerFollowing: string | null - viewerFollowedBy: string | null -} - -export type ProfileInfoMap = { [did: string]: ProfileInfo } - -export type ProfileHydrationState = { - profiles: ProfileInfoMap - labels: Labels - lists: ListInfoMap - bam: BlockAndMuteState -} - -export type ProfileDetailInfo = ProfileInfo & { - bannerCid: string | null - followsCount: number | null - followersCount: number | null - postsCount: number | null -} - -export type ProfileDetailInfoMap = { [did: string]: ProfileDetailInfo } - -export type ProfileDetailHydrationState = { - profilesDetailed: ProfileDetailInfoMap - labels: Labels - lists: ListInfoMap - bam: BlockAndMuteState -} - -export const toMapByDid = ( - items: T[], -): Record => { - return items.reduce((cur, item) => { - cur[item.did] = item - return cur - }, {} as Record) -} diff --git a/packages/bsky/src/services/actor/views.ts b/packages/bsky/src/services/actor/views.ts deleted file mode 100644 index 32e267a8868..00000000000 --- a/packages/bsky/src/services/actor/views.ts +++ /dev/null @@ -1,375 +0,0 @@ -import { mapDefined } from '@atproto/common' -import { INVALID_HANDLE } from '@atproto/syntax' -import { jsonStringToLex } from '@atproto/lexicon' -import { - ProfileViewDetailed, - ProfileView, -} from '../../lexicon/types/app/bsky/actor/defs' -import { Database } from '../../db' -import { noMatch, notSoftDeletedClause } from '../../db/util' -import { Actor } from '../../db/tables/actor' -import { ImageUriBuilder } from '../../image/uri' -import { LabelService, Labels, getSelfLabels } from '../label' -import { BlockAndMuteState, GraphService } from '../graph' -import { - ActorInfoMap, - ProfileDetailHydrationState, - ProfileHydrationState, - ProfileInfoMap, - ProfileViewMap, - toMapByDid, -} from './types' -import { ListInfoMap } from '../graph/types' -import { FromDb } from '../types' - -export class ActorViews { - services: { - label: LabelService - graph: GraphService - } - - constructor( - private db: Database, - private imgUriBuilder: ImageUriBuilder, - private graph: FromDb, - private label: FromDb, - ) { - this.services = { - label: label(db), - graph: graph(db), - } - } - - async profiles( - results: (ActorResult | string)[], // @TODO simplify down to just string[] - viewer: string | null, - opts?: { includeSoftDeleted?: boolean }, - ): Promise { - if (results.length === 0) return {} - const dids = results.map((res) => (typeof res === 'string' ? res : res.did)) - const hydrated = await this.profileHydration(dids, { - viewer, - ...opts, - }) - return this.profilePresentation(dids, hydrated, viewer) - } - - async profilesBasic( - results: (ActorResult | string)[], - viewer: string | null, - opts?: { includeSoftDeleted?: boolean }, - ): Promise { - if (results.length === 0) return {} - const dids = results.map((res) => (typeof res === 'string' ? res : res.did)) - const hydrated = await this.profileHydration(dids, { - viewer, - includeSoftDeleted: opts?.includeSoftDeleted, - }) - return this.profileBasicPresentation(dids, hydrated, viewer) - } - - async profilesList( - results: ActorResult[], - viewer: string | null, - opts?: { includeSoftDeleted?: boolean }, - ): Promise { - const profiles = await this.profiles(results, viewer, opts) - return mapDefined(results, (result) => profiles[result.did]) - } - - async profileDetailHydration( - dids: string[], - opts: { - viewer?: string | null - includeSoftDeleted?: boolean - }, - state?: { - bam: BlockAndMuteState - labels: Labels - }, - ): Promise { - const { viewer = null, includeSoftDeleted } = opts - const { ref } = this.db.db.dynamic - const profileInfosQb = this.db.db - .selectFrom('actor') - .where('actor.did', 'in', dids.length ? dids : ['']) - .leftJoin('profile', 'profile.creator', 'actor.did') - .leftJoin('profile_agg', 'profile_agg.did', 'actor.did') - .leftJoin('record', 'record.uri', 'profile.uri') - .if(!includeSoftDeleted, (qb) => - qb.where(notSoftDeletedClause(ref('actor'))), - ) - .select([ - 'actor.did as did', - 'actor.handle as handle', - 'profile.uri as profileUri', - 'profile.cid as profileCid', - 'profile.displayName as displayName', - 'profile.description as description', - 'profile.avatarCid as avatarCid', - 'profile.bannerCid as bannerCid', - 'profile.indexedAt as indexedAt', - 'profile_agg.followsCount as followsCount', - 'profile_agg.followersCount as followersCount', - 'profile_agg.postsCount as postsCount', - 'record.json as profileJson', - this.db.db - .selectFrom('follow') - .if(!viewer, (q) => q.where(noMatch)) - .where('creator', '=', viewer ?? '') - .whereRef('subjectDid', '=', ref('actor.did')) - .select('uri') - .as('viewerFollowing'), - this.db.db - .selectFrom('follow') - .if(!viewer, (q) => q.where(noMatch)) - .whereRef('creator', '=', ref('actor.did')) - .where('subjectDid', '=', viewer ?? '') - .select('uri') - .as('viewerFollowedBy'), - ]) - const [profiles, labels, bam] = await Promise.all([ - profileInfosQb.execute(), - this.services.label.getLabelsForSubjects(dids, state?.labels), - this.services.graph.getBlockAndMuteState( - viewer ? dids.map((did) => [viewer, did]) : [], - state?.bam, - ), - ]) - const listUris = mapDefined(profiles, ({ did }) => { - const muteList = viewer && bam.muteList([viewer, did]) - const blockList = viewer && bam.blockList([viewer, did]) - const lists: string[] = [] - if (muteList) lists.push(muteList) - if (blockList) lists.push(blockList) - return lists - }).flat() - const lists = await this.services.graph.getListViews(listUris, viewer) - return { profilesDetailed: toMapByDid(profiles), labels, bam, lists } - } - - profileDetailPresentation( - dids: string[], - state: ProfileDetailHydrationState, - opts: { - viewer?: string | null - }, - ): Record { - const { viewer } = opts - const { profilesDetailed, lists, labels, bam } = state - return dids.reduce((acc, did) => { - const prof = profilesDetailed[did] - if (!prof) return acc - const avatar = prof?.avatarCid - ? this.imgUriBuilder.getPresetUri('avatar', prof.did, prof.avatarCid) - : undefined - const banner = prof?.bannerCid - ? this.imgUriBuilder.getPresetUri('banner', prof.did, prof.bannerCid) - : undefined - const mutedByListUri = viewer && bam.muteList([viewer, did]) - const mutedByList = - mutedByListUri && lists[mutedByListUri] - ? this.services.graph.formatListViewBasic(lists[mutedByListUri]) - : undefined - const blockingByListUri = viewer && bam.blockList([viewer, did]) - const blockingByList = - blockingByListUri && lists[blockingByListUri] - ? this.services.graph.formatListViewBasic(lists[blockingByListUri]) - : undefined - const actorLabels = labels[did] ?? [] - const selfLabels = getSelfLabels({ - uri: prof.profileUri, - cid: prof.profileCid, - record: - prof.profileJson !== null - ? (jsonStringToLex(prof.profileJson) as Record) - : null, - }) - acc[did] = { - did: prof.did, - handle: prof.handle ?? INVALID_HANDLE, - displayName: prof?.displayName || undefined, - description: prof?.description || undefined, - avatar, - banner, - followsCount: prof?.followsCount ?? 0, - followersCount: prof?.followersCount ?? 0, - postsCount: prof?.postsCount ?? 0, - indexedAt: prof?.indexedAt || undefined, - viewer: viewer - ? { - muted: bam.mute([viewer, did]), - mutedByList, - blockedBy: !!bam.blockedBy([viewer, did]), - blocking: bam.blocking([viewer, did]) ?? undefined, - blockingByList, - following: - prof?.viewerFollowing && !bam.block([viewer, did]) - ? prof.viewerFollowing - : undefined, - followedBy: - prof?.viewerFollowedBy && !bam.block([viewer, did]) - ? prof.viewerFollowedBy - : undefined, - } - : undefined, - labels: [...actorLabels, ...selfLabels], - } - return acc - }, {} as Record) - } - - async profileHydration( - dids: string[], - opts: { - viewer?: string | null - includeSoftDeleted?: boolean - }, - state?: { - bam: BlockAndMuteState - labels: Labels - }, - ): Promise { - const { viewer = null, includeSoftDeleted } = opts - const { ref } = this.db.db.dynamic - const profileInfosQb = this.db.db - .selectFrom('actor') - .where('actor.did', 'in', dids.length ? dids : ['']) - .leftJoin('profile', 'profile.creator', 'actor.did') - .leftJoin('record', 'record.uri', 'profile.uri') - .if(!includeSoftDeleted, (qb) => - qb.where(notSoftDeletedClause(ref('actor'))), - ) - .select([ - 'actor.did as did', - 'actor.handle as handle', - 'profile.uri as profileUri', - 'profile.cid as profileCid', - 'profile.displayName as displayName', - 'profile.description as description', - 'profile.avatarCid as avatarCid', - 'profile.indexedAt as indexedAt', - 'record.json as profileJson', - this.db.db - .selectFrom('follow') - .if(!viewer, (q) => q.where(noMatch)) - .where('creator', '=', viewer ?? '') - .whereRef('subjectDid', '=', ref('actor.did')) - .select('uri') - .as('viewerFollowing'), - this.db.db - .selectFrom('follow') - .if(!viewer, (q) => q.where(noMatch)) - .whereRef('creator', '=', ref('actor.did')) - .where('subjectDid', '=', viewer ?? '') - .select('uri') - .as('viewerFollowedBy'), - ]) - const [profiles, labels, bam] = await Promise.all([ - profileInfosQb.execute(), - this.services.label.getLabelsForSubjects(dids, state?.labels), - this.services.graph.getBlockAndMuteState( - viewer ? dids.map((did) => [viewer, did]) : [], - state?.bam, - ), - ]) - const listUris = mapDefined(profiles, ({ did }) => { - const muteList = viewer && bam.muteList([viewer, did]) - const blockList = viewer && bam.blockList([viewer, did]) - const lists: string[] = [] - if (muteList) lists.push(muteList) - if (blockList) lists.push(blockList) - return lists - }).flat() - const lists = await this.services.graph.getListViews(listUris, viewer) - return { profiles: toMapByDid(profiles), labels, bam, lists } - } - - profilePresentation( - dids: string[], - state: { - profiles: ProfileInfoMap - lists: ListInfoMap - labels: Labels - bam: BlockAndMuteState - }, - viewer: string | null, - ): ProfileViewMap { - const { profiles, lists, labels, bam } = state - return dids.reduce((acc, did) => { - const prof = profiles[did] - if (!prof) return acc - const avatar = prof?.avatarCid - ? this.imgUriBuilder.getPresetUri('avatar', prof.did, prof.avatarCid) - : undefined - const mutedByListUri = viewer && bam.muteList([viewer, did]) - const mutedByList = - mutedByListUri && lists[mutedByListUri] - ? this.services.graph.formatListViewBasic(lists[mutedByListUri]) - : undefined - const blockingByListUri = viewer && bam.blockList([viewer, did]) - const blockingByList = - blockingByListUri && lists[blockingByListUri] - ? this.services.graph.formatListViewBasic(lists[blockingByListUri]) - : undefined - const actorLabels = labels[did] ?? [] - const selfLabels = getSelfLabels({ - uri: prof.profileUri, - cid: prof.profileCid, - record: - prof.profileJson !== null - ? (jsonStringToLex(prof.profileJson) as Record) - : null, - }) - acc[did] = { - did: prof.did, - handle: prof.handle ?? INVALID_HANDLE, - displayName: prof?.displayName || undefined, - description: prof?.description || undefined, - avatar, - indexedAt: prof?.indexedAt || undefined, - viewer: viewer - ? { - muted: bam.mute([viewer, did]), - mutedByList, - blockedBy: !!bam.blockedBy([viewer, did]), - blocking: bam.blocking([viewer, did]) ?? undefined, - blockingByList, - following: - prof?.viewerFollowing && !bam.block([viewer, did]) - ? prof.viewerFollowing - : undefined, - followedBy: - prof?.viewerFollowedBy && !bam.block([viewer, did]) - ? prof.viewerFollowedBy - : undefined, - } - : undefined, - labels: [...actorLabels, ...selfLabels], - } - return acc - }, {} as ProfileViewMap) - } - - profileBasicPresentation( - dids: string[], - state: ProfileHydrationState, - viewer: string | null, - ): ProfileViewMap { - const result = this.profilePresentation(dids, state, viewer) - return Object.values(result).reduce((acc, prof) => { - const profileBasic = { - did: prof.did, - handle: prof.handle, - displayName: prof.displayName, - avatar: prof.avatar, - viewer: prof.viewer, - labels: prof.labels, - } - acc[prof.did] = profileBasic - return acc - }, {} as ProfileViewMap) - } -} - -type ActorResult = Actor diff --git a/packages/bsky/src/services/feed/index.ts b/packages/bsky/src/services/feed/index.ts deleted file mode 100644 index 9b94fd6c714..00000000000 --- a/packages/bsky/src/services/feed/index.ts +++ /dev/null @@ -1,567 +0,0 @@ -import { sql } from 'kysely' -import { AtUri } from '@atproto/syntax' -import { jsonStringToLex } from '@atproto/lexicon' -import { mapDefined } from '@atproto/common' -import { Database } from '../../db' -import { countAll, noMatch, notSoftDeletedClause } from '../../db/util' -import { ImageUriBuilder } from '../../image/uri' -import { ids } from '../../lexicon/lexicons' -import { - Record as PostRecord, - isRecord as isPostRecord, -} from '../../lexicon/types/app/bsky/feed/post' -import { - Record as ThreadgateRecord, - isListRule, -} from '../../lexicon/types/app/bsky/feed/threadgate' -import { isMain as isEmbedImages } from '../../lexicon/types/app/bsky/embed/images' -import { isMain as isEmbedExternal } from '../../lexicon/types/app/bsky/embed/external' -import { - isMain as isEmbedRecord, - isViewRecord, -} from '../../lexicon/types/app/bsky/embed/record' -import { isMain as isEmbedRecordWithMedia } from '../../lexicon/types/app/bsky/embed/recordWithMedia' -import { - PostInfoMap, - FeedItemType, - FeedRow, - FeedGenInfoMap, - PostEmbedViews, - RecordEmbedViewRecordMap, - PostInfo, - RecordEmbedViewRecord, - PostBlocksMap, - FeedHydrationState, - ThreadgateInfoMap, -} from './types' -import { LabelService } from '../label' -import { ActorService } from '../actor' -import { - BlockAndMuteState, - GraphService, - ListInfoMap, - RelationshipPair, -} from '../graph' -import { FeedViews } from './views' -import { threadgateToPostUri, postToThreadgateUri } from './util' -import { FromDb } from '../types' - -export * from './types' - -export class FeedService { - views: FeedViews - services: { - label: LabelService - actor: ActorService - graph: GraphService - } - - constructor( - public db: Database, - public imgUriBuilder: ImageUriBuilder, - private actor: FromDb, - private label: FromDb, - private graph: FromDb, - ) { - this.views = new FeedViews(this.db, this.imgUriBuilder, actor, graph) - this.services = { - label: label(this.db), - actor: actor(this.db), - graph: graph(this.db), - } - } - - static creator( - imgUriBuilder: ImageUriBuilder, - actor: FromDb, - label: FromDb, - graph: FromDb, - ) { - return (db: Database) => - new FeedService(db, imgUriBuilder, actor, label, graph) - } - - selectPostQb() { - return this.db.db - .selectFrom('post') - .select([ - sql`${'post'}`.as('type'), - 'post.uri as uri', - 'post.cid as cid', - 'post.uri as postUri', - 'post.creator as originatorDid', - 'post.creator as postAuthorDid', - 'post.replyParent as replyParent', - 'post.replyRoot as replyRoot', - 'post.sortAt as sortAt', - ]) - } - - selectFeedItemQb() { - return this.db.db - .selectFrom('feed_item') - .innerJoin('post', 'post.uri', 'feed_item.postUri') - .selectAll('feed_item') - .select([ - 'post.replyRoot', - 'post.replyParent', - 'post.creator as postAuthorDid', - ]) - } - - selectFeedGeneratorQb(viewer?: string | null) { - const { ref } = this.db.db.dynamic - return this.db.db - .selectFrom('feed_generator') - .innerJoin('actor', 'actor.did', 'feed_generator.creator') - .innerJoin('record', 'record.uri', 'feed_generator.uri') - .selectAll('feed_generator') - .where(notSoftDeletedClause(ref('actor'))) - .where(notSoftDeletedClause(ref('record'))) - .select((qb) => - qb - .selectFrom('like') - .whereRef('like.subject', '=', 'feed_generator.uri') - .select(countAll.as('count')) - .as('likeCount'), - ) - .select((qb) => - qb - .selectFrom('like') - .if(!viewer, (q) => q.where(noMatch)) - .where('like.creator', '=', viewer ?? '') - .whereRef('like.subject', '=', 'feed_generator.uri') - .select('uri') - .as('viewerLike'), - ) - } - - async getPostInfos( - postUris: string[], - viewer: string | null, - includeSoftDeleted?: boolean, - ): Promise { - if (postUris.length < 1) return {} - const db = this.db.db - const { ref } = db.dynamic - const posts = await db - .selectFrom('post') - .where('post.uri', 'in', postUris) - .innerJoin('actor', 'actor.did', 'post.creator') - .innerJoin('record', 'record.uri', 'post.uri') - .leftJoin('post_agg', 'post_agg.uri', 'post.uri') - .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', - 'post.creator as creator', - 'post.sortAt as indexedAt', - 'post.invalidReplyRoot as invalidReplyRoot', - 'post.violatesThreadGate as violatesThreadGate', - 'record.json as recordJson', - 'post_agg.likeCount as likeCount', - 'post_agg.repostCount as repostCount', - 'post_agg.replyCount as replyCount', - 'post.tags as tags', - db - .selectFrom('repost') - .if(!viewer, (q) => q.where(noMatch)) - .where('creator', '=', viewer ?? '') - .whereRef('subject', '=', ref('post.uri')) - .select('uri') - .as('requesterRepost'), - db - .selectFrom('like') - .if(!viewer, (q) => q.where(noMatch)) - .where('creator', '=', viewer ?? '') - .whereRef('subject', '=', ref('post.uri')) - .select('uri') - .as('requesterLike'), - ]) - .execute() - return posts.reduce((acc, cur) => { - const { recordJson, ...post } = cur - const record = jsonStringToLex(recordJson) as PostRecord - const info: PostInfo = { - ...post, - invalidReplyRoot: post.invalidReplyRoot ?? false, - violatesThreadGate: post.violatesThreadGate ?? false, - record, - viewer, - } - return Object.assign(acc, { [post.uri]: info }) - }, {} as PostInfoMap) - } - - async getFeedGeneratorInfos(generatorUris: string[], viewer: string | null) { - if (generatorUris.length < 1) return {} - const feedGens = await this.selectFeedGeneratorQb(viewer) - .where('feed_generator.uri', 'in', generatorUris) - .execute() - return feedGens.reduce( - (acc, cur) => ({ - ...acc, - [cur.uri]: { - ...cur, - viewer: viewer ? { like: cur.viewerLike } : undefined, - }, - }), - {} as FeedGenInfoMap, - ) - } - - async getFeedItems(uris: string[]): Promise> { - if (uris.length < 1) return {} - const feedItems = await this.selectFeedItemQb() - .where('feed_item.uri', 'in', uris) - .execute() - return feedItems.reduce((acc, item) => { - return Object.assign(acc, { [item.uri]: item }) - }, {} as Record) - } - - async postUrisToFeedItems(uris: string[]): Promise { - const feedItems = await this.getFeedItems(uris) - return mapDefined(uris, (uri) => feedItems[uri]) - } - - feedItemRefs(items: FeedRow[]) { - const actorDids = new Set() - const postUris = new Set() - for (const item of items) { - postUris.add(item.postUri) - actorDids.add(item.postAuthorDid) - actorDids.add(item.originatorDid) - if (item.replyParent) { - postUris.add(item.replyParent) - actorDids.add(new AtUri(item.replyParent).hostname) - } - if (item.replyRoot) { - postUris.add(item.replyRoot) - actorDids.add(new AtUri(item.replyRoot).hostname) - } - } - return { dids: actorDids, uris: postUris } - } - - async feedHydration( - refs: { - dids: Set - uris: Set - viewer: string | null - includeSoftDeleted?: boolean - }, - depth = 0, - ): Promise { - const { viewer, dids, uris } = refs - const [posts, threadgates, labels, bam] = await Promise.all([ - this.getPostInfos(Array.from(uris), viewer, refs.includeSoftDeleted), - this.threadgatesByPostUri(Array.from(uris)), - this.services.label.getLabelsForSubjects([...uris, ...dids]), - this.services.graph.getBlockAndMuteState( - viewer ? [...dids].map((did) => [viewer, did]) : [], - ), - ]) - - // profileState for labels and bam handled above, profileHydration() shouldn't fetch additional - const [profileState, blocks, lists] = await Promise.all([ - this.services.actor.views.profileHydration( - Array.from(dids), - { viewer, includeSoftDeleted: refs.includeSoftDeleted }, - { bam, labels }, - ), - this.blocksForPosts(posts, bam), - this.listsForThreadgates(threadgates, viewer), - ]) - const embeds = await this.embedsForPosts(posts, blocks, viewer, depth) - return { - posts, - threadgates, - blocks, - embeds, - labels, // includes info for profiles - bam, // includes info for profiles - profiles: profileState.profiles, - lists: Object.assign(lists, profileState.lists), - } - } - - // applies blocks for visibility to third-parties (i.e. based on post content) - async blocksForPosts( - posts: PostInfoMap, - bam?: BlockAndMuteState, - ): Promise { - const relationships: RelationshipPair[] = [] - const byPost: Record = {} - const didFromUri = (uri) => new AtUri(uri).host - for (const post of Object.values(posts)) { - // skip posts that we can't process or appear to already have been processed - if (!isPostRecord(post.record)) continue - if (byPost[post.uri]) continue - byPost[post.uri] = {} - // 3p block for replies - const parentUri = post.record.reply?.parent.uri - const parentDid = parentUri ? didFromUri(parentUri) : null - // 3p block for record embeds - const embedUris = nestedRecordUris([post.record]) - // gather actor relationships among posts - if (parentDid) { - const pair: RelationshipPair = [post.creator, parentDid] - relationships.push(pair) - byPost[post.uri].reply = pair - } - for (const embedUri of embedUris) { - const pair: RelationshipPair = [post.creator, didFromUri(embedUri)] - relationships.push(pair) - byPost[post.uri].embed = pair - } - } - // compute block state from all actor relationships among posts - const blockState = await this.services.graph.getBlockState( - relationships, - bam, - ) - const result: PostBlocksMap = {} - Object.entries(byPost).forEach(([uri, block]) => { - if (block.embed && blockState.block(block.embed)) { - result[uri] ??= {} - result[uri].embed = true - } - if (block.reply && blockState.block(block.reply)) { - result[uri] ??= {} - result[uri].reply = true - } - }) - return result - } - - async embedsForPosts( - postInfos: PostInfoMap, - blocks: PostBlocksMap, - viewer: string | null, - depth: number, - ) { - const postMap = postRecordsFromInfos(postInfos) - const posts = Object.values(postMap) - if (posts.length < 1) { - return {} - } - const recordEmbedViews = - depth > 1 ? {} : await this.nestedRecordViews(posts, viewer, depth) - - const postEmbedViews: PostEmbedViews = {} - for (const [uri, post] of Object.entries(postMap)) { - const creator = new AtUri(uri).hostname - if (!post.embed) continue - if (isEmbedImages(post.embed)) { - postEmbedViews[uri] = this.views.imagesEmbedView(creator, post.embed) - } else if (isEmbedExternal(post.embed)) { - postEmbedViews[uri] = this.views.externalEmbedView(creator, post.embed) - } else if (isEmbedRecord(post.embed)) { - if (!recordEmbedViews[post.embed.record.uri]) continue - postEmbedViews[uri] = { - $type: 'app.bsky.embed.record#view', - record: applyEmbedBlock( - uri, - blocks, - recordEmbedViews[post.embed.record.uri], - ), - } - } else if (isEmbedRecordWithMedia(post.embed)) { - const embedRecordView = recordEmbedViews[post.embed.record.record.uri] - if (!embedRecordView) continue - const formatted = this.views.getRecordWithMediaEmbedView( - creator, - post.embed, - applyEmbedBlock(uri, blocks, embedRecordView), - ) - if (formatted) { - postEmbedViews[uri] = formatted - } - } - } - return postEmbedViews - } - - async nestedRecordViews( - posts: PostRecord[], - viewer: string | null, - depth: number, - ): Promise { - const nestedUris = nestedRecordUris(posts) - if (nestedUris.length < 1) return {} - const nestedDids = new Set() - const nestedPostUris = new Set() - const nestedFeedGenUris = new Set() - const nestedListUris = new Set() - for (const uri of nestedUris) { - const parsed = new AtUri(uri) - nestedDids.add(parsed.hostname) - if (parsed.collection === ids.AppBskyFeedPost) { - nestedPostUris.add(uri) - } else if (parsed.collection === ids.AppBskyFeedGenerator) { - nestedFeedGenUris.add(uri) - } else if (parsed.collection === ids.AppBskyGraphList) { - nestedListUris.add(uri) - } - } - const [feedState, feedGenInfos, listViews] = await Promise.all([ - this.feedHydration( - { - dids: nestedDids, - uris: nestedPostUris, - viewer, - }, - depth + 1, - ), - this.getFeedGeneratorInfos([...nestedFeedGenUris], viewer), - this.services.graph.getListViews([...nestedListUris], viewer), - ]) - const actorInfos = this.services.actor.views.profileBasicPresentation( - [...nestedDids], - feedState, - viewer, - ) - const recordEmbedViews: RecordEmbedViewRecordMap = {} - for (const uri of nestedUris) { - const collection = new AtUri(uri).collection - if (collection === ids.AppBskyFeedGenerator && feedGenInfos[uri]) { - const genView = this.views.formatFeedGeneratorView( - feedGenInfos[uri], - actorInfos, - ) - if (genView) { - recordEmbedViews[uri] = { - $type: 'app.bsky.feed.defs#generatorView', - ...genView, - } - } - } else if (collection === ids.AppBskyGraphList && listViews[uri]) { - const listView = this.services.graph.formatListView( - listViews[uri], - actorInfos, - ) - if (listView) { - recordEmbedViews[uri] = { - $type: 'app.bsky.graph.defs#listView', - ...listView, - } - } - } else if (collection === ids.AppBskyFeedPost && feedState.posts[uri]) { - const formatted = this.views.formatPostView( - uri, - actorInfos, - feedState.posts, - feedState.threadgates, - feedState.embeds, - feedState.labels, - feedState.lists, - viewer, - ) - recordEmbedViews[uri] = this.views.getRecordEmbedView( - uri, - formatted, - depth > 0, - ) - } else { - recordEmbedViews[uri] = { - $type: 'app.bsky.embed.record#viewNotFound', - uri, - notFound: true, - } - } - } - return recordEmbedViews - } - - async threadgatesByPostUri(postUris: string[]): Promise { - const gates = postUris.length - ? await this.db.db - .selectFrom('record') - .where('uri', 'in', postUris.map(postToThreadgateUri)) - .select(['uri', 'cid', 'json']) - .execute() - : [] - const gatesByPostUri = gates.reduce((acc, gate) => { - const record = jsonStringToLex(gate.json) as ThreadgateRecord - const postUri = threadgateToPostUri(gate.uri) - if (record.post !== postUri) return acc // invalid, skip - acc[postUri] = { uri: gate.uri, cid: gate.cid, record } - return acc - }, {} as ThreadgateInfoMap) - return gatesByPostUri - } - - listsForThreadgates( - threadgates: ThreadgateInfoMap, - viewer: string | null, - ): Promise { - const listsUris = new Set() - Object.values(threadgates).forEach((gate) => { - gate?.record.allow?.forEach((rule) => { - if (isListRule(rule)) { - listsUris.add(rule.list) - } - }) - }) - return this.services.graph.getListViews([...listsUris], viewer) - } -} - -const postRecordsFromInfos = ( - infos: PostInfoMap, -): { [uri: string]: PostRecord } => { - const records: { [uri: string]: PostRecord } = {} - for (const [uri, info] of Object.entries(infos)) { - if (isPostRecord(info.record)) { - records[uri] = info.record - } - } - return records -} - -const nestedRecordUris = (posts: PostRecord[]): string[] => { - const uris: string[] = [] - for (const post of posts) { - if (!post.embed) continue - if (isEmbedRecord(post.embed)) { - uris.push(post.embed.record.uri) - } else if (isEmbedRecordWithMedia(post.embed)) { - uris.push(post.embed.record.record.uri) - } else { - continue - } - } - return uris -} - -type PostRelationships = { reply?: RelationshipPair; embed?: RelationshipPair } - -function applyEmbedBlock( - uri: string, - blocks: PostBlocksMap, - view: RecordEmbedViewRecord, -): RecordEmbedViewRecord { - if (isViewRecord(view) && blocks[uri]?.embed) { - return { - $type: 'app.bsky.embed.record#viewBlocked', - uri: view.uri, - blocked: true, - author: { - did: view.author.did, - viewer: view.author.viewer - ? { - blockedBy: view.author.viewer?.blockedBy, - blocking: view.author.viewer?.blocking, - } - : undefined, - }, - } - } - return view -} diff --git a/packages/bsky/src/services/feed/types.ts b/packages/bsky/src/services/feed/types.ts deleted file mode 100644 index 8d4bd67f6bb..00000000000 --- a/packages/bsky/src/services/feed/types.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Selectable } from 'kysely' -import { Record as ThreadgateRecord } from '../../lexicon/types/app/bsky/feed/threadgate' -import { View as ImagesEmbedView } from '../../lexicon/types/app/bsky/embed/images' -import { View as ExternalEmbedView } from '../../lexicon/types/app/bsky/embed/external' -import { - ViewBlocked, - ViewNotFound, - ViewRecord, - View as RecordEmbedView, -} from '../../lexicon/types/app/bsky/embed/record' -import { View as RecordWithMediaEmbedView } from '../../lexicon/types/app/bsky/embed/recordWithMedia' -import { - BlockedPost, - GeneratorView, - NotFoundPost, - PostView, -} from '../../lexicon/types/app/bsky/feed/defs' -import { FeedGenerator } from '../../db/tables/feed-generator' -import { ListView } from '../../lexicon/types/app/bsky/graph/defs' -import { ProfileHydrationState } from '../actor' -import { Labels } from '../label' -import { BlockAndMuteState } from '../graph' - -export type PostEmbedViews = { - [uri: string]: PostEmbedView -} - -export type PostEmbedView = - | ImagesEmbedView - | ExternalEmbedView - | RecordEmbedView - | RecordWithMediaEmbedView - -export type PostInfo = { - uri: string - cid: string - creator: string - record: Record - indexedAt: string - likeCount: number | null - repostCount: number | null - replyCount: number | null - requesterRepost: string | null - requesterLike: string | null - invalidReplyRoot: boolean - violatesThreadGate: boolean - viewer: string | null -} - -export type PostInfoMap = { [uri: string]: PostInfo } - -export type PostBlocksMap = { - [uri: string]: { reply?: boolean; embed?: boolean } -} - -export type ThreadgateInfo = { - uri: string - cid: string - record: ThreadgateRecord -} - -export type ThreadgateInfoMap = { - [postUri: string]: ThreadgateInfo -} - -export type FeedGenInfo = Selectable & { - likeCount: number - viewer?: { - like?: string - } -} - -export type FeedGenInfoMap = { [uri: string]: FeedGenInfo } - -export type FeedItemType = 'post' | 'repost' - -export type FeedRow = { - type: FeedItemType - uri: string - cid: string - postUri: string - postAuthorDid: string - originatorDid: string - replyParent: string | null - replyRoot: string | null - sortAt: string -} - -export type MaybePostView = PostView | NotFoundPost | BlockedPost - -export type RecordEmbedViewRecord = - | ViewRecord - | ViewNotFound - | ViewBlocked - | GeneratorView - | ListView - -export type RecordEmbedViewRecordMap = { [uri: string]: RecordEmbedViewRecord } - -export type FeedHydrationState = ProfileHydrationState & { - posts: PostInfoMap - threadgates: ThreadgateInfoMap - embeds: PostEmbedViews - labels: Labels - blocks: PostBlocksMap - bam: BlockAndMuteState -} diff --git a/packages/bsky/src/services/feed/views.ts b/packages/bsky/src/services/feed/views.ts deleted file mode 100644 index ac733032acd..00000000000 --- a/packages/bsky/src/services/feed/views.ts +++ /dev/null @@ -1,475 +0,0 @@ -import { mapDefined } from '@atproto/common' -import { AtUri } from '@atproto/syntax' -import { Database } from '../../db' -import { - FeedViewPost, - GeneratorView, - PostView, -} from '../../lexicon/types/app/bsky/feed/defs' -import { - Main as EmbedImages, - isMain as isEmbedImages, - View as EmbedImagesView, -} from '../../lexicon/types/app/bsky/embed/images' -import { - Main as EmbedExternal, - isMain as isEmbedExternal, - View as EmbedExternalView, -} from '../../lexicon/types/app/bsky/embed/external' -import { Main as EmbedRecordWithMedia } from '../../lexicon/types/app/bsky/embed/recordWithMedia' -import { - ViewBlocked, - ViewNotFound, - ViewRecord, -} from '../../lexicon/types/app/bsky/embed/record' -import { Record as PostRecord } from '../../lexicon/types/app/bsky/feed/post' -import { isListRule } from '../../lexicon/types/app/bsky/feed/threadgate' -import { - PostEmbedViews, - FeedGenInfo, - FeedRow, - MaybePostView, - PostInfoMap, - RecordEmbedViewRecord, - PostBlocksMap, - FeedHydrationState, - ThreadgateInfoMap, - ThreadgateInfo, -} from './types' -import { Labels, getSelfLabels } from '../label' -import { ImageUriBuilder } from '../../image/uri' -import { ActorInfoMap, ActorService } from '../actor' -import { ListInfoMap, GraphService } from '../graph' -import { FromDb } from '../types' -import { parseThreadGate } from './util' - -export class FeedViews { - services: { - actor: ActorService - graph: GraphService - } - - constructor( - public db: Database, - public imgUriBuilder: ImageUriBuilder, - private actor: FromDb, - private graph: FromDb, - ) { - this.services = { - actor: actor(this.db), - graph: graph(this.db), - } - } - - static creator( - imgUriBuilder: ImageUriBuilder, - actor: FromDb, - graph: FromDb, - ) { - return (db: Database) => new FeedViews(db, imgUriBuilder, actor, graph) - } - - formatFeedGeneratorView( - info: FeedGenInfo, - profiles: ActorInfoMap, - ): GeneratorView | undefined { - const profile = profiles[info.creator] - if (!profile) { - return undefined - } - return { - uri: info.uri, - cid: info.cid, - did: info.feedDid, - creator: profile, - displayName: info.displayName ?? undefined, - description: info.description ?? undefined, - descriptionFacets: info.descriptionFacets - ? JSON.parse(info.descriptionFacets) - : undefined, - avatar: info.avatarCid - ? this.imgUriBuilder.getPresetUri( - 'avatar', - info.creator, - info.avatarCid, - ) - : undefined, - likeCount: info.likeCount, - viewer: info.viewer - ? { - like: info.viewer.like ?? undefined, - } - : undefined, - indexedAt: info.indexedAt, - } - } - - formatFeed( - items: FeedRow[], - state: FeedHydrationState, - viewer: string | null, - opts?: { - usePostViewUnion?: boolean - }, - ): FeedViewPost[] { - const { posts, threadgates, profiles, blocks, embeds, labels, lists } = - state - const actors = this.services.actor.views.profileBasicPresentation( - Object.keys(profiles), - state, - viewer, - ) - const feed: FeedViewPost[] = [] - for (const item of items) { - const info = posts[item.postUri] - const post = this.formatPostView( - item.postUri, - actors, - posts, - threadgates, - embeds, - labels, - lists, - viewer, - ) - // skip over not found post - if (!post) { - continue - } - const feedPost = { post } - if (item.type === 'repost') { - const originator = actors[item.originatorDid] - // skip over reposts where we don't have reposter profile - if (!originator) { - continue - } else { - feedPost['reason'] = { - $type: 'app.bsky.feed.defs#reasonRepost', - by: originator, - indexedAt: item.sortAt, - } - } - } - // posts that violate reply-gating may appear in feeds, but without any thread context - if ( - item.replyParent && - item.replyRoot && - !info?.invalidReplyRoot && - !info?.violatesThreadGate - ) { - const replyParent = this.formatMaybePostView( - item.replyParent, - item.uri, - actors, - posts, - threadgates, - embeds, - labels, - lists, - blocks, - viewer, - opts, - ) - const replyRoot = this.formatMaybePostView( - item.replyRoot, - item.uri, - actors, - posts, - threadgates, - embeds, - labels, - lists, - blocks, - viewer, - opts, - ) - if (replyRoot && replyParent) { - feedPost['reply'] = { - root: replyRoot, - parent: replyParent, - } - } - } - feed.push(feedPost) - } - return feed - } - - formatPostView( - uri: string, - actors: ActorInfoMap, - posts: PostInfoMap, - threadgates: ThreadgateInfoMap, - embeds: PostEmbedViews, - labels: Labels, - lists: ListInfoMap, - viewer: string | null, - ): PostView | undefined { - const post = posts[uri] - const gate = threadgates[uri] - const author = actors[post?.creator] - if (!post || !author) { - return undefined - } - const postLabels = labels[uri] ?? [] - const postSelfLabels = getSelfLabels({ - uri: post.uri, - cid: post.cid, - record: post.record, - }) - return { - uri: post.uri, - cid: post.cid, - author: author, - record: post.record, - embed: embeds[uri], - replyCount: post.replyCount ?? 0, - repostCount: post.repostCount ?? 0, - likeCount: post.likeCount ?? 0, - indexedAt: post.indexedAt, - viewer: post.viewer - ? { - repost: post.requesterRepost ?? undefined, - like: post.requesterLike ?? undefined, - replyDisabled: this.userReplyDisabled( - uri, - actors, - posts, - threadgates, - lists, - viewer, - ), - } - : undefined, - labels: [...postLabels, ...postSelfLabels], - threadgate: - !post.record.reply && gate - ? this.formatThreadgate(gate, lists) - : undefined, - } - } - - userReplyDisabled( - uri: string, - actors: ActorInfoMap, - posts: PostInfoMap, - threadgates: ThreadgateInfoMap, - lists: ListInfoMap, - viewer: string | null, - ): boolean | undefined { - if (viewer === null) { - return undefined - } else if (posts[uri]?.violatesThreadGate) { - return true - } - - const rootUriStr: string = - posts[uri]?.record?.['reply']?.['root']?.['uri'] ?? uri - const gate = threadgates[rootUriStr]?.record - if (!gate) { - return undefined - } - const rootPost = posts[rootUriStr]?.record as PostRecord | undefined - const ownerDid = new AtUri(rootUriStr).hostname - - const { - canReply, - allowFollowing, - allowListUris = [], - } = parseThreadGate(viewer, ownerDid, rootPost ?? null, gate ?? null) - - if (canReply) { - return false - } - if (allowFollowing && actors[ownerDid]?.viewer?.followedBy) { - return false - } - for (const listUri of allowListUris) { - const list = lists[listUri] - if (list?.viewerInList) { - return false - } - } - return true - } - - formatMaybePostView( - uri: string, - replyUri: string | null, - actors: ActorInfoMap, - posts: PostInfoMap, - threadgates: ThreadgateInfoMap, - embeds: PostEmbedViews, - labels: Labels, - lists: ListInfoMap, - blocks: PostBlocksMap, - viewer: string | null, - opts?: { - usePostViewUnion?: boolean - }, - ): MaybePostView | undefined { - const post = this.formatPostView( - uri, - actors, - posts, - threadgates, - embeds, - labels, - lists, - viewer, - ) - if (!post) { - if (!opts?.usePostViewUnion) return - return this.notFoundPost(uri) - } - if ( - post.author.viewer?.blockedBy || - post.author.viewer?.blocking || - (replyUri !== null && blocks[replyUri]?.reply) - ) { - if (!opts?.usePostViewUnion) return - return this.blockedPost(post) - } - return { - $type: 'app.bsky.feed.defs#postView', - ...post, - } - } - - blockedPost(post: PostView) { - return { - $type: 'app.bsky.feed.defs#blockedPost', - uri: post.uri, - blocked: true as const, - author: { - did: post.author.did, - viewer: post.author.viewer - ? { - blockedBy: post.author.viewer?.blockedBy, - blocking: post.author.viewer?.blocking, - } - : undefined, - }, - } - } - - notFoundPost(uri: string) { - return { - $type: 'app.bsky.feed.defs#notFoundPost', - uri: uri, - notFound: true as const, - } - } - - imagesEmbedView(did: string, embed: EmbedImages) { - const imgViews = embed.images.map((img) => ({ - thumb: this.imgUriBuilder.getPresetUri( - 'feed_thumbnail', - did, - img.image.ref, - ), - fullsize: this.imgUriBuilder.getPresetUri( - 'feed_fullsize', - did, - img.image.ref, - ), - alt: img.alt, - aspectRatio: img.aspectRatio, - })) - return { - $type: 'app.bsky.embed.images#view', - images: imgViews, - } - } - - externalEmbedView(did: string, embed: EmbedExternal) { - const { uri, title, description, thumb } = embed.external - return { - $type: 'app.bsky.embed.external#view', - external: { - uri, - title, - description, - thumb: thumb - ? this.imgUriBuilder.getPresetUri('feed_thumbnail', did, thumb.ref) - : undefined, - }, - } - } - - getRecordEmbedView( - uri: string, - post?: PostView, - omitEmbeds = false, - ): (ViewRecord | ViewNotFound | ViewBlocked) & { $type: string } { - if (!post) { - return { - $type: 'app.bsky.embed.record#viewNotFound', - uri, - notFound: true, - } - } - if (post.author.viewer?.blocking || post.author.viewer?.blockedBy) { - return { - $type: 'app.bsky.embed.record#viewBlocked', - uri, - blocked: true, - author: { - did: post.author.did, - viewer: post.author.viewer - ? { - blockedBy: post.author.viewer?.blockedBy, - blocking: post.author.viewer?.blocking, - } - : undefined, - }, - } - } - return { - $type: 'app.bsky.embed.record#viewRecord', - uri: post.uri, - cid: post.cid, - author: post.author, - value: post.record, - labels: post.labels, - indexedAt: post.indexedAt, - embeds: omitEmbeds ? undefined : post.embed ? [post.embed] : [], - } - } - - getRecordWithMediaEmbedView( - did: string, - embed: EmbedRecordWithMedia, - embedRecordView: RecordEmbedViewRecord, - ) { - let mediaEmbed: EmbedImagesView | EmbedExternalView - if (isEmbedImages(embed.media)) { - mediaEmbed = this.imagesEmbedView(did, embed.media) - } else if (isEmbedExternal(embed.media)) { - mediaEmbed = this.externalEmbedView(did, embed.media) - } else { - return - } - return { - $type: 'app.bsky.embed.recordWithMedia#view', - record: { - record: embedRecordView, - }, - media: mediaEmbed, - } - } - - formatThreadgate(gate: ThreadgateInfo, lists: ListInfoMap) { - return { - uri: gate.uri, - cid: gate.cid, - record: gate.record, - lists: mapDefined(gate.record.allow ?? [], (rule) => { - if (!isListRule(rule)) return - const list = lists[rule.list] - if (!list) return - return this.services.graph.formatListViewBasic(list) - }), - } - } -} diff --git a/packages/bsky/src/services/graph/index.ts b/packages/bsky/src/services/graph/index.ts deleted file mode 100644 index bc6d3d05677..00000000000 --- a/packages/bsky/src/services/graph/index.ts +++ /dev/null @@ -1,404 +0,0 @@ -import { sql } from 'kysely' -import { Database } from '../../db' -import { ImageUriBuilder } from '../../image/uri' -import { DbRef, Subquery, valuesList } from '../../db/util' -import { ListInfo } from './types' -import { ActorInfoMap } from '../actor' -import { - ListView, - ListViewBasic, -} from '../../lexicon/types/app/bsky/graph/defs' - -export * from './types' - -export class GraphService { - constructor(public db: Database, public imgUriBuilder: ImageUriBuilder) {} - - static creator(imgUriBuilder: ImageUriBuilder) { - return (db: Database) => new GraphService(db, imgUriBuilder) - } - - async muteActor(info: { - subjectDid: string - mutedByDid: string - createdAt?: Date - }) { - const { subjectDid, mutedByDid, createdAt = new Date() } = info - await this.db - .asPrimary() - .db.insertInto('mute') - .values({ - subjectDid, - mutedByDid, - createdAt: createdAt.toISOString(), - }) - .onConflict((oc) => oc.doNothing()) - .execute() - } - - async unmuteActor(info: { subjectDid: string; mutedByDid: string }) { - const { subjectDid, mutedByDid } = info - await this.db - .asPrimary() - .db.deleteFrom('mute') - .where('subjectDid', '=', subjectDid) - .where('mutedByDid', '=', mutedByDid) - .execute() - } - - async muteActorList(info: { - list: string - mutedByDid: string - createdAt?: Date - }) { - const { list, mutedByDid, createdAt = new Date() } = info - await this.db - .asPrimary() - .db.insertInto('list_mute') - .values({ - listUri: list, - mutedByDid, - createdAt: createdAt.toISOString(), - }) - .onConflict((oc) => oc.doNothing()) - .execute() - } - - async unmuteActorList(info: { list: string; mutedByDid: string }) { - const { list, mutedByDid } = info - await this.db - .asPrimary() - .db.deleteFrom('list_mute') - .where('listUri', '=', list) - .where('mutedByDid', '=', mutedByDid) - .execute() - } - - getListsQb(viewer: string | null) { - const { ref } = this.db.db.dynamic - return this.db.db - .selectFrom('list') - .innerJoin('actor', 'actor.did', 'list.creator') - .selectAll('list') - .selectAll('actor') - .select('list.sortAt as sortAt') - .select([ - this.db.db - .selectFrom('list_mute') - .where('list_mute.mutedByDid', '=', viewer ?? '') - .whereRef('list_mute.listUri', '=', ref('list.uri')) - .select('list_mute.listUri') - .as('viewerMuted'), - this.db.db - .selectFrom('list_block') - .where('list_block.creator', '=', viewer ?? '') - .whereRef('list_block.subjectUri', '=', ref('list.uri')) - .select('list_block.uri') - .as('viewerListBlockUri'), - this.db.db - .selectFrom('list_item') - .whereRef('list_item.listUri', '=', ref('list.uri')) - .where('list_item.subjectDid', '=', viewer ?? '') - .select('list_item.uri') - .as('viewerInList'), - ]) - } - - getListItemsQb() { - return this.db.db - .selectFrom('list_item') - .innerJoin('actor as subject', 'subject.did', 'list_item.subjectDid') - .selectAll('subject') - .select([ - 'list_item.uri as uri', - 'list_item.cid as cid', - 'list_item.sortAt as sortAt', - ]) - } - - async getBlockAndMuteState( - pairs: RelationshipPair[], - bam?: BlockAndMuteState, - ) { - pairs = bam ? pairs.filter((pair) => !bam.has(pair)) : pairs - const result = bam ?? new BlockAndMuteState() - if (!pairs.length) return result - const { ref } = this.db.db.dynamic - const sourceRef = ref('pair.source') - const targetRef = ref('pair.target') - const values = valuesList(pairs.map((p) => sql`${p[0]}, ${p[1]}`)) - const items = await this.db.db - .selectFrom(values.as(sql`pair (source, target)`)) - .select([ - sql`${sourceRef}`.as('source'), - sql`${targetRef}`.as('target'), - this.db.db - .selectFrom('actor_block') - .whereRef('creator', '=', sourceRef) - .whereRef('subjectDid', '=', targetRef) - .select('uri') - .as('blocking'), - this.db.db - .selectFrom('list_item') - .innerJoin('list_block', 'list_block.subjectUri', 'list_item.listUri') - .whereRef('list_block.creator', '=', sourceRef) - .whereRef('list_item.subjectDid', '=', targetRef) - .whereExists((qb) => - this.modListSubquery(qb, ref('list_item.listUri')), - ) - .select('list_item.listUri') - .limit(1) - .as('blockingViaList'), - this.db.db - .selectFrom('actor_block') - .whereRef('creator', '=', targetRef) - .whereRef('subjectDid', '=', sourceRef) - .select('uri') - .as('blockedBy'), - this.db.db - .selectFrom('list_item') - .innerJoin('list_block', 'list_block.subjectUri', 'list_item.listUri') - .whereRef('list_block.creator', '=', targetRef) - .whereRef('list_item.subjectDid', '=', sourceRef) - .whereExists((qb) => - this.modListSubquery(qb, ref('list_item.listUri')), - ) - .select('list_item.listUri') - .limit(1) - .as('blockedByViaList'), - this.db.db - .selectFrom('mute') - .whereRef('mutedByDid', '=', sourceRef) - .whereRef('subjectDid', '=', targetRef) - .select(sql`${true}`.as('val')) - .as('muting'), - this.db.db - .selectFrom('list_item') - .innerJoin('list_mute', 'list_mute.listUri', 'list_item.listUri') - .whereRef('list_mute.mutedByDid', '=', sourceRef) - .whereRef('list_item.subjectDid', '=', targetRef) - .whereExists((qb) => - this.modListSubquery(qb, ref('list_item.listUri')), - ) - .select('list_item.listUri') - .limit(1) - .as('mutingViaList'), - ]) - .selectAll() - .execute() - items.forEach((item) => result.add(item)) - return result - } - - modListSubquery(qb: Subquery, ref: DbRef) { - return qb - .selectFrom('list') - .select('uri') - .where('list.purpose', '=', 'app.bsky.graph.defs#modlist') - .whereRef('list.uri', '=', ref) - } - - async getBlockState(pairs: RelationshipPair[], bam?: BlockAndMuteState) { - pairs = bam ? pairs.filter((pair) => !bam.has(pair)) : pairs - const result = bam ?? new BlockAndMuteState() - if (!pairs.length) return result - const { ref } = this.db.db.dynamic - const sourceRef = ref('pair.source') - const targetRef = ref('pair.target') - const values = valuesList(pairs.map((p) => sql`${p[0]}, ${p[1]}`)) - const items = await this.db.db - .selectFrom(values.as(sql`pair (source, target)`)) - .select([ - sql`${sourceRef}`.as('source'), - sql`${targetRef}`.as('target'), - this.db.db - .selectFrom('actor_block') - .whereRef('creator', '=', sourceRef) - .whereRef('subjectDid', '=', targetRef) - .select('uri') - .as('blocking'), - this.db.db - .selectFrom('list_item') - .innerJoin('list_block', 'list_block.subjectUri', 'list_item.listUri') - .whereRef('list_block.creator', '=', sourceRef) - .whereRef('list_item.subjectDid', '=', targetRef) - .whereExists((qb) => - this.modListSubquery(qb, ref('list_item.listUri')), - ) - .select('list_item.listUri') - .limit(1) - .as('blockingViaList'), - this.db.db - .selectFrom('actor_block') - .whereRef('creator', '=', targetRef) - .whereRef('subjectDid', '=', sourceRef) - .select('uri') - .as('blockedBy'), - this.db.db - .selectFrom('list_item') - .innerJoin('list_block', 'list_block.subjectUri', 'list_item.listUri') - .whereRef('list_block.creator', '=', targetRef) - .whereRef('list_item.subjectDid', '=', sourceRef) - .whereExists((qb) => - this.modListSubquery(qb, ref('list_item.listUri')), - ) - .select('list_item.listUri') - .limit(1) - .as('blockedByViaList'), - ]) - .selectAll() - .execute() - items.forEach((item) => result.add(item)) - return result - } - - async getListViews(listUris: string[], requester: string | null) { - if (listUris.length < 1) return {} - const lists = await this.getListsQb(requester) - .where('list.uri', 'in', listUris) - .execute() - return lists.reduce( - (acc, cur) => ({ - ...acc, - [cur.uri]: cur, - }), - {}, - ) - } - - formatListView(list: ListInfo, profiles: ActorInfoMap): ListView | undefined { - if (!profiles[list.creator]) { - return undefined - } - return { - ...this.formatListViewBasic(list), - creator: profiles[list.creator], - description: list.description ?? undefined, - descriptionFacets: list.descriptionFacets - ? JSON.parse(list.descriptionFacets) - : undefined, - indexedAt: list.sortAt, - } - } - - formatListViewBasic(list: ListInfo): ListViewBasic { - return { - uri: list.uri, - cid: list.cid, - name: list.name, - purpose: list.purpose, - avatar: list.avatarCid - ? this.imgUriBuilder.getPresetUri( - 'avatar', - list.creator, - list.avatarCid, - ) - : undefined, - indexedAt: list.sortAt, - viewer: { - muted: !!list.viewerMuted, - blocked: list.viewerListBlockUri ?? undefined, - }, - } - } -} - -export type RelationshipPair = [didA: string, didB: string] - -export class BlockAndMuteState { - hasIdx = new Map>() // did -> did - blockIdx = new Map>() // did -> did -> block uri - blockListIdx = new Map>() // did -> did -> list uri - muteIdx = new Map>() // did -> did - muteListIdx = new Map>() // did -> did -> list uri - constructor(items: BlockAndMuteInfo[] = []) { - items.forEach((item) => this.add(item)) - } - add(item: BlockAndMuteInfo) { - if (item.source === item.target) { - return // we do not respect self-blocks or self-mutes - } - if (item.blocking) { - const map = this.blockIdx.get(item.source) ?? new Map() - map.set(item.target, item.blocking) - if (!this.blockIdx.has(item.source)) { - this.blockIdx.set(item.source, map) - } - } - if (item.blockingViaList) { - const map = this.blockListIdx.get(item.source) ?? new Map() - map.set(item.target, item.blockingViaList) - if (!this.blockListIdx.has(item.source)) { - this.blockListIdx.set(item.source, map) - } - } - if (item.blockedBy) { - const map = this.blockIdx.get(item.target) ?? new Map() - map.set(item.source, item.blockedBy) - if (!this.blockIdx.has(item.target)) { - this.blockIdx.set(item.target, map) - } - } - if (item.blockedByViaList) { - const map = this.blockListIdx.get(item.target) ?? new Map() - map.set(item.source, item.blockedByViaList) - if (!this.blockListIdx.has(item.target)) { - this.blockListIdx.set(item.target, map) - } - } - if (item.muting) { - const set = this.muteIdx.get(item.source) ?? new Set() - set.add(item.target) - if (!this.muteIdx.has(item.source)) { - this.muteIdx.set(item.source, set) - } - } - if (item.mutingViaList) { - const map = this.muteListIdx.get(item.source) ?? new Map() - map.set(item.target, item.mutingViaList) - if (!this.muteListIdx.has(item.source)) { - this.muteListIdx.set(item.source, map) - } - } - const set = this.hasIdx.get(item.source) ?? new Set() - set.add(item.target) - if (!this.hasIdx.has(item.source)) { - this.hasIdx.set(item.source, set) - } - } - block(pair: RelationshipPair): boolean { - return !!this.blocking(pair) || !!this.blockedBy(pair) - } - // block or list uri - blocking(pair: RelationshipPair): string | null { - return this.blockIdx.get(pair[0])?.get(pair[1]) ?? this.blockList(pair) - } - // block or list uri - blockedBy(pair: RelationshipPair): string | null { - return this.blocking([pair[1], pair[0]]) - } - mute(pair: RelationshipPair): boolean { - return !!this.muteIdx.get(pair[0])?.has(pair[1]) || !!this.muteList(pair) - } - // list uri - blockList(pair: RelationshipPair): string | null { - return this.blockListIdx.get(pair[0])?.get(pair[1]) ?? null - } - muteList(pair: RelationshipPair): string | null { - return this.muteListIdx.get(pair[0])?.get(pair[1]) ?? null - } - has(pair: RelationshipPair) { - return !!this.hasIdx.get(pair[0])?.has(pair[1]) - } -} - -type BlockAndMuteInfo = { - source: string - target: string - blocking?: string | null - blockingViaList?: string | null - blockedBy?: string | null - blockedByViaList?: string | null - muting?: true | null - mutingViaList?: string | null -} diff --git a/packages/bsky/src/services/graph/types.ts b/packages/bsky/src/services/graph/types.ts deleted file mode 100644 index 5ff254dc383..00000000000 --- a/packages/bsky/src/services/graph/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Selectable } from 'kysely' -import { List } from '../../db/tables/list' - -export type ListInfo = Selectable & { - viewerMuted: string | null - viewerListBlockUri: string | null - viewerInList: string | null -} - -export type ListInfoMap = Record diff --git a/packages/bsky/src/services/index.ts b/packages/bsky/src/services/index.ts deleted file mode 100644 index 2e5b4725681..00000000000 --- a/packages/bsky/src/services/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { ImageUriBuilder } from '../image/uri' -import { ActorService } from './actor' -import { FeedService } from './feed' -import { GraphService } from './graph' -import { ModerationService } from './moderation' -import { LabelCacheOpts, LabelService } from './label' -import { ImageInvalidator } from '../image/invalidator' -import { FromDb, FromDbPrimary } from './types' - -export function createServices(resources: { - imgUriBuilder: ImageUriBuilder - imgInvalidator: ImageInvalidator - labelCacheOpts: LabelCacheOpts -}): Services { - const { imgUriBuilder, imgInvalidator, labelCacheOpts } = resources - const label = LabelService.creator(labelCacheOpts) - const graph = GraphService.creator(imgUriBuilder) - const actor = ActorService.creator(imgUriBuilder, graph, label) - const moderation = ModerationService.creator(imgUriBuilder, imgInvalidator) - const feed = FeedService.creator(imgUriBuilder, actor, label, graph) - return { - actor, - feed, - moderation, - graph, - label, - } -} - -export type Services = { - actor: FromDb - feed: FromDb - graph: FromDb - moderation: FromDbPrimary - label: FromDb -} diff --git a/packages/bsky/src/services/label/index.ts b/packages/bsky/src/services/label/index.ts deleted file mode 100644 index f4c11295da7..00000000000 --- a/packages/bsky/src/services/label/index.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { sql } from 'kysely' -import { AtUri, normalizeDatetimeAlways } from '@atproto/syntax' -import { Database } from '../../db' -import { Label, isSelfLabels } from '../../lexicon/types/com/atproto/label/defs' -import { ids } from '../../lexicon/lexicons' -import { ReadThroughCache } from '../../cache/read-through' -import { Redis } from '../../redis' - -export type Labels = Record - -export type LabelCacheOpts = { - redis: Redis - staleTTL: number - maxTTL: number -} - -export class LabelService { - public cache: ReadThroughCache | null - - constructor(public db: Database, cacheOpts: LabelCacheOpts | null) { - if (cacheOpts) { - this.cache = new ReadThroughCache(cacheOpts.redis, { - ...cacheOpts, - fetchMethod: async (subject: string) => { - const res = await fetchLabelsForSubjects(db, [subject]) - return res[subject] ?? [] - }, - fetchManyMethod: (subjects: string[]) => - fetchLabelsForSubjects(db, subjects), - }) - } - } - - static creator(cacheOpts: LabelCacheOpts | null) { - return (db: Database) => new LabelService(db, cacheOpts) - } - - async formatAndCreate( - src: string, - uri: string, - cid: string | null, - labels: { create?: string[]; negate?: string[] }, - ): Promise { - const { create = [], negate = [] } = labels - const toCreate = create.map((val) => ({ - src, - uri, - cid: cid ?? undefined, - val, - neg: false, - cts: new Date().toISOString(), - })) - const toNegate = negate.map((val) => ({ - src, - uri, - cid: cid ?? undefined, - val, - neg: true, - cts: new Date().toISOString(), - })) - const formatted = [...toCreate, ...toNegate] - await this.createLabels(formatted) - return formatted - } - - async createLabels(labels: Label[]) { - if (labels.length < 1) return - const dbVals = labels.map((l) => ({ - ...l, - cid: l.cid ?? '', - neg: !!l.neg, - })) - const { ref } = this.db.db.dynamic - const excluded = (col: string) => ref(`excluded.${col}`) - await this.db - .asPrimary() - .db.insertInto('label') - .values(dbVals) - .onConflict((oc) => - oc.columns(['src', 'uri', 'cid', 'val']).doUpdateSet({ - neg: sql`${excluded('neg')}`, - cts: sql`${excluded('cts')}`, - }), - ) - .execute() - } - - async getLabelsForUris( - subjects: string[], - opts?: { - includeNeg?: boolean - skipCache?: boolean - }, - ): Promise { - if (subjects.length < 1) return {} - const res = this.cache - ? await this.cache.getMany(subjects, { revalidate: opts?.skipCache }) - : await fetchLabelsForSubjects(this.db, subjects) - - if (opts?.includeNeg) { - return res - } - - const noNegs: Labels = {} - for (const [key, val] of Object.entries(res)) { - noNegs[key] = val.filter((label) => !label.neg) - } - return noNegs - } - - // gets labels for any record. when did is present, combine labels for both did & profile record. - async getLabelsForSubjects( - subjects: string[], - opts?: { - includeNeg?: boolean - skipCache?: boolean - }, - labels: Labels = {}, - ): Promise { - if (subjects.length < 1) return labels - const expandedSubjects = subjects.flatMap((subject) => { - if (labels[subject]) return [] // skip over labels we already have fetched - if (subject.startsWith('did:')) { - return [ - subject, - AtUri.make(subject, ids.AppBskyActorProfile, 'self').toString(), - ] - } - return subject - }) - const labelsByUri = await this.getLabelsForUris(expandedSubjects, opts) - return Object.keys(labelsByUri).reduce((acc, cur) => { - const uri = cur.startsWith('at://') ? new AtUri(cur) : null - if ( - uri && - uri.collection === ids.AppBskyActorProfile && - uri.rkey === 'self' - ) { - // combine labels for profile + did - const did = uri.hostname - acc[did] ??= [] - acc[did].push(...labelsByUri[cur]) - } - acc[cur] ??= [] - acc[cur].push(...labelsByUri[cur]) - return acc - }, labels) - } - - async getLabels( - subject: string, - opts?: { - includeNeg?: boolean - skipCache?: boolean - }, - ): Promise { - const labels = await this.getLabelsForUris([subject], opts) - return labels[subject] ?? [] - } - - async getLabelsForProfile( - did: string, - opts?: { - includeNeg?: boolean - skipCache?: boolean - }, - ): Promise { - const labels = await this.getLabelsForSubjects([did], opts) - return labels[did] ?? [] - } -} - -export function getSelfLabels(details: { - uri: string | null - cid: string | null - record: Record | null -}): Label[] { - const { uri, cid, record } = details - if (!uri || !cid || !record) return [] - if (!isSelfLabels(record.labels)) return [] - const src = new AtUri(uri).host // record creator - const cts = - typeof record.createdAt === 'string' - ? normalizeDatetimeAlways(record.createdAt) - : new Date(0).toISOString() - return record.labels.values.map(({ val }) => { - return { src, uri, cid, val, cts, neg: false } - }) -} - -const fetchLabelsForSubjects = async ( - db: Database, - subjects: string[], -): Promise> => { - if (subjects.length === 0) { - return {} - } - const res = await db.db - .selectFrom('label') - .where('label.uri', 'in', subjects) - .selectAll() - .execute() - const labelMap = res.reduce((acc, cur) => { - acc[cur.uri] ??= [] - acc[cur.uri].push({ - ...cur, - cid: cur.cid === '' ? undefined : cur.cid, - neg: cur.neg, - }) - return acc - }, {} as Record) - // ensure we cache negatives - for (const subject of subjects) { - labelMap[subject] ??= [] - } - return labelMap -} diff --git a/packages/bsky/src/services/moderation/index.ts b/packages/bsky/src/services/moderation/index.ts deleted file mode 100644 index 71380e16884..00000000000 --- a/packages/bsky/src/services/moderation/index.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { CID } from 'multiformats/cid' -import { AtUri } from '@atproto/syntax' -import { PrimaryDatabase } from '../../db' -import { ImageUriBuilder } from '../../image/uri' -import { ImageInvalidator } from '../../image/invalidator' -import { StatusAttr } from '../../lexicon/types/com/atproto/admin/defs' - -export class ModerationService { - constructor( - public db: PrimaryDatabase, - public imgUriBuilder: ImageUriBuilder, - public imgInvalidator: ImageInvalidator, - ) {} - - static creator( - imgUriBuilder: ImageUriBuilder, - imgInvalidator: ImageInvalidator, - ) { - return (db: PrimaryDatabase) => - new ModerationService(db, imgUriBuilder, imgInvalidator) - } - - async takedownRepo(info: { takedownRef: string; did: string }) { - const { takedownRef, did } = info - await this.db.db - .updateTable('actor') - .set({ takedownRef }) - .where('did', '=', did) - .where('takedownRef', 'is', null) - .executeTakeFirst() - } - - async reverseTakedownRepo(info: { did: string }) { - await this.db.db - .updateTable('actor') - .set({ takedownRef: null }) - .where('did', '=', info.did) - .execute() - } - - async takedownRecord(info: { takedownRef: string; uri: AtUri; cid: CID }) { - const { takedownRef, uri } = info - await this.db.db - .updateTable('record') - .set({ takedownRef }) - .where('uri', '=', uri.toString()) - .where('takedownRef', 'is', null) - .executeTakeFirst() - } - - async reverseTakedownRecord(info: { uri: AtUri }) { - await this.db.db - .updateTable('record') - .set({ takedownRef: null }) - .where('uri', '=', info.uri.toString()) - .execute() - } - - async takedownBlob(info: { takedownRef: string; did: string; cid: string }) { - const { takedownRef, did, cid } = info - await this.db.db - .insertInto('blob_takedown') - .values({ did, cid, takedownRef }) - .onConflict((oc) => oc.doNothing()) - .execute() - const paths = ImageUriBuilder.presets.map((id) => { - const imgUri = this.imgUriBuilder.getPresetUri(id, did, cid) - return imgUri.replace(this.imgUriBuilder.endpoint, '') - }) - await this.imgInvalidator.invalidate(cid.toString(), paths) - } - - async reverseTakedownBlob(info: { did: string; cid: string }) { - const { did, cid } = info - await this.db.db - .deleteFrom('blob_takedown') - .where('did', '=', did) - .where('cid', '=', cid) - .execute() - } - - async getRepoTakedownRef(did: string): Promise { - const res = await this.db.db - .selectFrom('actor') - .where('did', '=', did) - .selectAll() - .executeTakeFirst() - return res ? formatStatus(res.takedownRef) : null - } - - async getRecordTakedownRef(uri: string): Promise { - const res = await this.db.db - .selectFrom('record') - .where('uri', '=', uri) - .selectAll() - .executeTakeFirst() - return res ? formatStatus(res.takedownRef) : null - } - - async getBlobTakedownRef( - did: string, - cid: string, - ): Promise { - const res = await this.db.db - .selectFrom('blob_takedown') - .where('did', '=', did) - .where('cid', '=', cid) - .selectAll() - .executeTakeFirst() - // this table only tracks takedowns not all blobs - // so if no result is returned then the blob is not taken down (rather than not found) - return formatStatus(res?.takedownRef ?? null) - } -} - -const formatStatus = (ref: string | null): StatusAttr => { - return ref ? { applied: true, ref } : { applied: false } -} diff --git a/packages/bsky/src/services/types.ts b/packages/bsky/src/services/types.ts deleted file mode 100644 index 2039d6c07de..00000000000 --- a/packages/bsky/src/services/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Database, PrimaryDatabase } from '../db' - -export type FromDb = (db: Database) => T -export type FromDbPrimary = (db: PrimaryDatabase) => T diff --git a/packages/bsky/src/services/util/notification.ts b/packages/bsky/src/services/util/notification.ts deleted file mode 100644 index e8eb618cff6..00000000000 --- a/packages/bsky/src/services/util/notification.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { sql } from 'kysely' -import { countAll } from '../../db/util' -import { PrimaryDatabase } from '../../db' - -// i.e. 30 days before the last time the user checked their notifs -export const BEFORE_LAST_SEEN_DAYS = 30 -// i.e. 180 days before the latest unread notification -export const BEFORE_LATEST_UNREAD_DAYS = 180 -// don't consider culling unreads until they hit this threshold, and then enforce beforeLatestUnreadThresholdDays -export const UNREAD_KEPT_COUNT = 500 - -export const tidyNotifications = async (db: PrimaryDatabase, did: string) => { - const stats = await db.db - .selectFrom('notification') - .select([ - sql<0 | 1>`("sortAt" < "lastSeenNotifs")`.as('read'), - countAll.as('count'), - sql`min("sortAt")`.as('earliestAt'), - sql`max("sortAt")`.as('latestAt'), - sql`max("lastSeenNotifs")`.as('lastSeenAt'), - ]) - .leftJoin('actor_state', 'actor_state.did', 'notification.did') - .where('notification.did', '=', did) - .groupBy(sql`1`) // group by read (i.e. 1st column) - .execute() - const readStats = stats.find((stat) => stat.read) - const unreadStats = stats.find((stat) => !stat.read) - let readCutoffAt: Date | undefined - let unreadCutoffAt: Date | undefined - if (readStats) { - readCutoffAt = addDays( - new Date(readStats.lastSeenAt), - -BEFORE_LAST_SEEN_DAYS, - ) - } - if (unreadStats && unreadStats.count > UNREAD_KEPT_COUNT) { - unreadCutoffAt = addDays( - new Date(unreadStats.latestAt), - -BEFORE_LATEST_UNREAD_DAYS, - ) - } - // take most recent of read/unread cutoffs - const cutoffAt = greatest(readCutoffAt, unreadCutoffAt) - if (cutoffAt) { - // skip delete if it won't catch any notifications - const earliestAt = least(readStats?.earliestAt, unreadStats?.earliestAt) - if (earliestAt && earliestAt < cutoffAt.toISOString()) { - await db.db - .deleteFrom('notification') - .where('did', '=', did) - .where('sortAt', '<', cutoffAt.toISOString()) - .execute() - } - } -} - -const addDays = (date: Date, days: number) => { - date.setDate(date.getDate() + days) - return date -} - -const least = (a: T | undefined, b: T | undefined) => { - return a !== undefined && (b === undefined || a < b) ? a : b -} - -const greatest = (a: T | undefined, b: T | undefined) => { - return a !== undefined && (b === undefined || a > b) ? a : b -} - -type Ordered = string | number | Date diff --git a/packages/bsky/src/services/util/post.ts b/packages/bsky/src/services/util/post.ts deleted file mode 100644 index 19e7fa3ee2c..00000000000 --- a/packages/bsky/src/services/util/post.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { sql } from 'kysely' -import DatabaseSchema from '../../db/database-schema' - -export const getDescendentsQb = ( - db: DatabaseSchema, - opts: { - uri: string - depth: number // required, protects against cycles - }, -) => { - const { uri, depth } = opts - const query = db.withRecursive('descendent(uri, depth)', (cte) => { - return cte - .selectFrom('post') - .select(['post.uri as uri', sql`1`.as('depth')]) - .where(sql`1`, '<=', depth) - .where('replyParent', '=', uri) - .unionAll( - cte - .selectFrom('post') - .innerJoin('descendent', 'descendent.uri', 'post.replyParent') - .where('descendent.depth', '<', depth) - .select([ - 'post.uri as uri', - sql`descendent.depth + 1`.as('depth'), - ]), - ) - }) - return query -} - -export const getAncestorsAndSelfQb = ( - db: DatabaseSchema, - opts: { - uri: string - parentHeight: number // required, protects against cycles - }, -) => { - const { uri, parentHeight } = opts - const query = db.withRecursive( - 'ancestor(uri, ancestorUri, height)', - (cte) => { - return cte - .selectFrom('post') - .select([ - 'post.uri as uri', - 'post.replyParent as ancestorUri', - sql`0`.as('height'), - ]) - .where('uri', '=', uri) - .unionAll( - cte - .selectFrom('post') - .innerJoin('ancestor', 'ancestor.ancestorUri', 'post.uri') - .where('ancestor.height', '<', parentHeight) - .select([ - 'post.uri as uri', - 'post.replyParent as ancestorUri', - sql`ancestor.height + 1`.as('height'), - ]), - ) - }, - ) - return query -} diff --git a/packages/bsky/src/services/util/search.ts b/packages/bsky/src/services/util/search.ts deleted file mode 100644 index 994d2f43879..00000000000 --- a/packages/bsky/src/services/util/search.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { sql } from 'kysely' -import { InvalidRequestError } from '@atproto/xrpc-server' -import { Database } from '../../db' -import { notSoftDeletedClause, DbRef, AnyQb } from '../../db/util' -import { GenericKeyset, paginate } from '../../db/pagination' - -export const getUserSearchQuery = ( - db: Database, - opts: { - query: string - limit: number - cursor?: string - includeSoftDeleted?: boolean - }, -) => { - const { ref } = db.db.dynamic - const { query, limit, cursor, includeSoftDeleted } = opts - // Matching user accounts based on handle - const distanceAccount = distance(query, ref('handle')) - let accountsQb = getMatchingAccountsQb(db, { query, includeSoftDeleted }) - accountsQb = paginate(accountsQb, { - limit, - cursor, - direction: 'asc', - keyset: new SearchKeyset(distanceAccount, ref('actor.did')), - }) - // Matching profiles based on display name - const distanceProfile = distance(query, ref('displayName')) - let profilesQb = getMatchingProfilesQb(db, { query, includeSoftDeleted }) - profilesQb = paginate(profilesQb, { - limit, - cursor, - direction: 'asc', - keyset: new SearchKeyset(distanceProfile, ref('actor.did')), - }) - // Combine and paginate result set - return paginate(combineAccountsAndProfilesQb(db, accountsQb, profilesQb), { - limit, - cursor, - direction: 'asc', - keyset: new SearchKeyset(ref('distance'), ref('actor.did')), - }) -} - -// Takes maximal advantage of trigram index at the expense of ability to paginate. -export const getUserSearchQuerySimple = ( - db: Database, - opts: { - query: string - limit: number - }, -) => { - const { ref } = db.db.dynamic - const { query, limit } = opts - // Matching user accounts based on handle - const accountsQb = getMatchingAccountsQb(db, { query }) - .orderBy('distance', 'asc') - .limit(limit) - // Matching profiles based on display name - const profilesQb = getMatchingProfilesQb(db, { query }) - .orderBy('distance', 'asc') - .limit(limit) - // Combine and paginate result set - return paginate(combineAccountsAndProfilesQb(db, accountsQb, profilesQb), { - limit, - direction: 'asc', - keyset: new SearchKeyset(ref('distance'), ref('actor.did')), - }) -} - -// Matching user accounts based on handle -const getMatchingAccountsQb = ( - db: Database, - opts: { query: string; includeSoftDeleted?: boolean }, -) => { - const { ref } = db.db.dynamic - const { query, includeSoftDeleted } = opts - const distanceAccount = distance(query, ref('handle')) - return db.db - .selectFrom('actor') - .if(!includeSoftDeleted, (qb) => - qb.where(notSoftDeletedClause(ref('actor'))), - ) - .where('actor.handle', 'is not', null) - .where(similar(query, ref('handle'))) // Coarse filter engaging trigram index - .select(['actor.did as did', distanceAccount.as('distance')]) -} - -// Matching profiles based on display name -const getMatchingProfilesQb = ( - db: Database, - opts: { query: string; includeSoftDeleted?: boolean }, -) => { - const { ref } = db.db.dynamic - const { query, includeSoftDeleted } = opts - const distanceProfile = distance(query, ref('displayName')) - return db.db - .selectFrom('profile') - .innerJoin('actor', 'actor.did', 'profile.creator') - .if(!includeSoftDeleted, (qb) => - qb.where(notSoftDeletedClause(ref('actor'))), - ) - .where('actor.handle', 'is not', null) - .where(similar(query, ref('displayName'))) // Coarse filter engaging trigram index - .select(['profile.creator as did', distanceProfile.as('distance')]) -} - -// Combine profile and account result sets -const combineAccountsAndProfilesQb = ( - db: Database, - accountsQb: AnyQb, - profilesQb: AnyQb, -) => { - // Combine user account and profile results, taking best matches from each - const emptyQb = db.db - .selectFrom('actor') - .where(sql`1 = 0`) - .select([sql.literal('').as('did'), sql`0`.as('distance')]) - const resultsQb = db.db - .selectFrom( - emptyQb - .unionAll(sql`${accountsQb}`) // The sql`` is adding parens - .unionAll(sql`${profilesQb}`) - .as('accounts_and_profiles'), - ) - .selectAll() - .distinctOn('did') // Per did, take whichever of account and profile distance is best - .orderBy('did') - .orderBy('distance') - return db.db - .selectFrom(resultsQb.as('results')) - .innerJoin('actor', 'actor.did', 'results.did') -} - -// Remove leading @ in case a handle is input that way -export const cleanQuery = (query: string) => query.trim().replace(/^@/g, '') - -// Uses pg_trgm strict word similarity to check similarity between a search query and a stored value -const distance = (query: string, ref: DbRef) => - sql`(${query} <<-> ${ref})` - -// Can utilize trigram index to match on strict word similarity. -// The word_similarity_threshold is set to .4 (i.e. distance < .6) in db/index.ts. -const similar = (query: string, ref: DbRef) => - sql`(${query} <% ${ref})` - -type Result = { distance: number; did: string } -type LabeledResult = { primary: number; secondary: string } -export class SearchKeyset extends GenericKeyset { - labelResult(result: Result) { - return { - primary: result.distance, - secondary: result.did, - } - } - labeledResultToCursor(labeled: LabeledResult) { - return { - primary: labeled.primary.toString().replace('0.', '.'), - secondary: labeled.secondary, - } - } - cursorToLabeledResult(cursor: { primary: string; secondary: string }) { - const distance = parseFloat(cursor.primary) - if (isNaN(distance)) { - throw new InvalidRequestError('Malformed cursor') - } - return { - primary: distance, - secondary: cursor.secondary, - } - } -} diff --git a/packages/bsky/src/views/index.ts b/packages/bsky/src/views/index.ts new file mode 100644 index 00000000000..99450f0491c --- /dev/null +++ b/packages/bsky/src/views/index.ts @@ -0,0 +1,854 @@ +import { AtUri, INVALID_HANDLE, normalizeDatetimeAlways } from '@atproto/syntax' +import { mapDefined } from '@atproto/common' +import { ImageUriBuilder } from '../image/uri' +import { HydrationState } from '../hydration/hydrator' +import { ids } from '../lexicon/lexicons' +import { + ProfileViewDetailed, + ProfileView, + ProfileViewBasic, + ViewerState as ProfileViewerState, +} from '../lexicon/types/app/bsky/actor/defs' +import { + BlockedPost, + FeedViewPost, + GeneratorView, + NotFoundPost, + PostView, + ReasonRepost, + ThreadViewPost, + ThreadgateView, +} from '../lexicon/types/app/bsky/feed/defs' +import { ListView, ListViewBasic } from '../lexicon/types/app/bsky/graph/defs' +import { creatorFromUri, parseThreadGate, cidFromBlobJson } from './util' +import { isListRule } from '../lexicon/types/app/bsky/feed/threadgate' +import { isSelfLabels } from '../lexicon/types/com/atproto/label/defs' +import { + Embed, + EmbedBlocked, + EmbedNotFound, + EmbedView, + ExternalEmbed, + ExternalEmbedView, + ImagesEmbed, + ImagesEmbedView, + MaybePostView, + NotificationView, + PostEmbedView, + RecordEmbed, + RecordEmbedView, + RecordEmbedViewInternal, + RecordWithMedia, + RecordWithMediaView, + isExternalEmbed, + isImagesEmbed, + isRecordEmbed, + isRecordWithMedia, +} from './types' +import { Label } from '../hydration/label' +import { FeedItem, Post, Repost } from '../hydration/feed' +import { RecordInfo } from '../hydration/util' +import { Notification } from '../proto/bsky_pb' + +export class Views { + constructor(public imgUriBuilder: ImageUriBuilder) {} + + // Actor + // ------------ + + actorIsTakendown(did: string, state: HydrationState): boolean { + return !!state.actors?.get(did)?.takedownRef + } + + viewerBlockExists(did: string, state: HydrationState): boolean { + const actor = state.profileViewers?.get(did) + if (!actor) return false + return ( + !!actor.blockedBy || + !!actor.blocking || + !!actor.blockedByList || + !!actor.blockingByList + ) + } + + viewerMuteExists(did: string, state: HydrationState): boolean { + const actor = state.profileViewers?.get(did) + if (!actor) return false + return actor.muted || !!actor.mutedByList + } + + profileDetailed( + did: string, + state: HydrationState, + ): ProfileViewDetailed | undefined { + const actor = state.actors?.get(did) + if (!actor) return + const baseView = this.profile(did, state) + if (!baseView) return + const profileAggs = state.profileAggs?.get(did) + return { + ...baseView, + banner: actor.profile?.banner + ? this.imgUriBuilder.getPresetUri( + 'banner', + did, + cidFromBlobJson(actor.profile.banner), + ) + : undefined, + followersCount: profileAggs?.followers, + followsCount: profileAggs?.follows, + postsCount: profileAggs?.posts, + } + } + + profile(did: string, state: HydrationState): ProfileView | undefined { + const actor = state.actors?.get(did) + if (!actor) return + const basicView = this.profileBasic(did, state) + if (!basicView) return + return { + ...basicView, + description: actor.profile?.description || undefined, + indexedAt: actor.sortedAt?.toISOString(), + } + } + + profileBasic( + did: string, + state: HydrationState, + ): ProfileViewBasic | undefined { + const actor = state.actors?.get(did) + if (!actor) return + const profileUri = AtUri.make( + did, + ids.AppBskyActorProfile, + 'self', + ).toString() + const labels = [ + ...(state.labels?.get(did) ?? []), + ...(state.labels?.get(profileUri) ?? []), + ...this.selfLabels({ + uri: profileUri, + cid: actor.profileCid?.toString(), + record: actor.profile, + }), + ] + return { + did, + handle: actor.handle ?? INVALID_HANDLE, + displayName: actor.profile?.displayName, + avatar: actor.profile?.avatar + ? this.imgUriBuilder.getPresetUri( + 'avatar', + did, + cidFromBlobJson(actor.profile.avatar), + ) + : undefined, + viewer: this.profileViewer(did, state), + labels, + } + } + + profileViewer( + did: string, + state: HydrationState, + ): ProfileViewerState | undefined { + const viewer = state.profileViewers?.get(did) + if (!viewer) return + const blockedByUri = viewer.blockedBy || viewer.blockedByList + const blockingUri = viewer.blocking || viewer.blockingByList + const block = !!blockedByUri || !!blockingUri + return { + muted: viewer.muted || !!viewer.mutedByList, + mutedByList: viewer.mutedByList + ? this.listBasic(viewer.mutedByList, state) + : undefined, + blockedBy: !!blockedByUri, + blocking: blockingUri, + blockingByList: viewer.blockingByList + ? this.listBasic(viewer.blockingByList, state) + : undefined, + following: viewer.following && !block ? viewer.following : undefined, + followedBy: viewer.followedBy && !block ? viewer.followedBy : undefined, + } + } + + blockedProfileViewer( + did: string, + state: HydrationState, + ): ProfileViewerState | undefined { + const viewer = state.profileViewers?.get(did) + if (!viewer) return + const blockedByUri = viewer.blockedBy || viewer.blockedByList + const blockingUri = viewer.blocking || viewer.blockingByList + return { + blockedBy: !!blockedByUri, + blocking: blockingUri, + } + } + + // Graph + // ------------ + + list(uri: string, state: HydrationState): ListView | undefined { + const creatorDid = new AtUri(uri).hostname + const list = state.lists?.get(uri) + if (!list) return + const creator = this.profile(creatorDid, state) + if (!creator) return + const basicView = this.listBasic(uri, state) + if (!basicView) return + + return { + ...basicView, + creator, + description: list.record.description, + descriptionFacets: list.record.descriptionFacets, + indexedAt: list.sortedAt.toISOString(), + } + } + + listBasic(uri: string, state: HydrationState): ListViewBasic | undefined { + const list = state.lists?.get(uri) + if (!list) { + return undefined + } + const listViewer = state.listViewers?.get(uri) + const creator = new AtUri(uri).hostname + return { + uri, + cid: list.cid, + name: list.record.name, + purpose: list.record.purpose, + avatar: list.record.avatar + ? this.imgUriBuilder.getPresetUri( + 'avatar', + creator, + cidFromBlobJson(list.record.avatar), + ) + : undefined, + indexedAt: list.sortedAt.toISOString(), + viewer: listViewer + ? { + muted: !!listViewer.viewerMuted, + blocked: listViewer.viewerListBlockUri, + } + : undefined, + } + } + + // Labels + // ------------ + + selfLabels(details: { + uri?: string + cid?: string + record?: Record + }): Label[] { + const { uri, cid, record } = details + if (!uri || !cid || !record) return [] + if (!isSelfLabels(record.labels)) return [] + const src = new AtUri(uri).host // record creator + const cts = + typeof record.createdAt === 'string' + ? normalizeDatetimeAlways(record.createdAt) + : new Date(0).toISOString() + return record.labels.values.map(({ val }) => { + return { src, uri, cid, val, cts, neg: false } + }) + } + + // Feed + // ------------ + + feedItemBlocksAndMutes( + item: FeedItem, + state: HydrationState, + ): { + originatorMuted: boolean + originatorBlocked: boolean + authorMuted: boolean + authorBlocked: boolean + } { + const authorDid = creatorFromUri(item.post.uri) + const originatorDid = item.repost + ? creatorFromUri(item.repost.uri) + : authorDid + return { + originatorMuted: this.viewerMuteExists(originatorDid, state), + originatorBlocked: this.viewerBlockExists(originatorDid, state), + authorMuted: this.viewerMuteExists(authorDid, state), + authorBlocked: this.viewerBlockExists(authorDid, state), + } + } + + feedGenerator(uri: string, state: HydrationState): GeneratorView | undefined { + const feedgen = state.feedgens?.get(uri) + if (!feedgen) return + const creatorDid = creatorFromUri(uri) + const creator = this.profile(creatorDid, state) + if (!creator) return + const viewer = state.feedgenViewers?.get(uri) + const aggs = state.feedgenAggs?.get(uri) + + return { + uri, + cid: feedgen.cid, + did: feedgen.record.did, + creator, + displayName: feedgen.record.displayName, + description: feedgen.record.description, + descriptionFacets: feedgen.record.descriptionFacets, + avatar: feedgen.record.avatar + ? this.imgUriBuilder.getPresetUri( + 'avatar', + creatorDid, + cidFromBlobJson(feedgen.record.avatar), + ) + : undefined, + likeCount: aggs?.likes, + viewer: viewer + ? { + like: viewer.like, + } + : undefined, + indexedAt: feedgen.sortedAt.toISOString(), + } + } + + threadGate(uri: string, state: HydrationState): ThreadgateView | undefined { + const gate = state.threadgates?.get(uri) + if (!gate) return + return { + uri, + cid: gate.cid, + record: gate.record, + lists: mapDefined(gate.record.allow ?? [], (rule) => { + if (!isListRule(rule)) return + return this.listBasic(rule.list, state) + }), + } + } + + post(uri: string, state: HydrationState, depth = 0): PostView | undefined { + const post = state.posts?.get(uri) + if (!post) return + const parsedUri = new AtUri(uri) + const authorDid = parsedUri.hostname + const author = this.profileBasic(authorDid, state) + if (!author) return + const aggs = state.postAggs?.get(uri) + const viewer = state.postViewers?.get(uri) + const gateUri = AtUri.make( + authorDid, + ids.AppBskyFeedThreadgate, + parsedUri.rkey, + ).toString() + const labels = [ + ...(state.labels?.get(uri) ?? []), + ...this.selfLabels({ + uri, + cid: post.cid, + record: post.record, + }), + ] + return { + uri, + cid: post.cid, + author, + record: post.record, + embed: + depth < 2 && post.record.embed + ? this.embed(uri, post.record.embed, state, depth + 1) + : undefined, + replyCount: aggs?.replies, + repostCount: aggs?.reposts, + likeCount: aggs?.likes, + indexedAt: post.sortedAt.toISOString(), + viewer: viewer + ? { + repost: viewer.repost, + like: viewer.like, + replyDisabled: this.userReplyDisabled(uri, state), + } + : undefined, + labels, + threadgate: !post.record.reply // only hydrate gate on root post + ? this.threadGate(gateUri, state) + : undefined, + } + } + + feedViewPost( + item: FeedItem, + state: HydrationState, + ): FeedViewPost | undefined { + const postInfo = state.posts?.get(item.post.uri) + let reason: ReasonRepost | undefined + if (item.repost) { + const repost = state.reposts?.get(item.repost.uri) + if (!repost) return + if (repost.record.subject.uri !== item.post.uri) return + reason = this.reasonRepost(creatorFromUri(item.repost.uri), repost, state) + if (!reason) return + } + const post = this.post(item.post.uri, state) + if (!post) return + return { + post, + reason, + reply: !postInfo?.violatesThreadGate + ? this.replyRef(item.post.uri, state) + : undefined, + } + } + + replyRef(uri: string, state: HydrationState, usePostViewUnion = false) { + // don't hydrate reply if there isn't it violates a block + if (state.postBlocks?.get(uri)?.reply) return undefined + const postRecord = state.posts?.get(uri.toString())?.record + if (!postRecord?.reply) return + const root = this.maybePost( + postRecord.reply.root.uri, + state, + usePostViewUnion, + ) + const parent = this.maybePost( + postRecord.reply.parent.uri, + state, + usePostViewUnion, + ) + return root && parent ? { root, parent } : undefined + } + + maybePost( + uri: string, + state: HydrationState, + usePostViewUnion = false, + ): MaybePostView | undefined { + const post = this.post(uri, state) + if (!post) return usePostViewUnion ? this.notFoundPost(uri) : undefined + if (this.viewerBlockExists(post.author.did, state)) { + return usePostViewUnion + ? this.blockedPost(uri, post.author.did, state) + : undefined + } + return { + $type: 'app.bsky.feed.defs#postView', + ...post, + } + } + + blockedPost( + uri: string, + authorDid: string, + state: HydrationState, + ): BlockedPost { + return { + $type: 'app.bsky.feed.defs#blockedPost', + uri, + blocked: true, + author: { + did: authorDid, + viewer: this.blockedProfileViewer(authorDid, state), + }, + } + } + + notFoundPost(uri: string): NotFoundPost { + return { + $type: 'app.bsky.feed.defs#notFoundPost', + uri, + notFound: true, + } + } + + reasonRepost( + creatorDid: string, + repost: Repost, + state: HydrationState, + ): ReasonRepost | undefined { + const creator = this.profileBasic(creatorDid, state) + if (!creator) return + return { + $type: 'app.bsky.feed.defs#reasonRepost', + by: creator, + indexedAt: repost.sortedAt.toISOString(), + } + } + + // Threads + // ------------ + + thread( + skele: { anchor: string; uris: string[] }, + state: HydrationState, + opts: { height: number; depth: number }, + ): ThreadViewPost | NotFoundPost | BlockedPost { + const { anchor, uris } = skele + const post = this.post(anchor, state) + const postInfo = state.posts?.get(anchor) + if (!postInfo || !post) return this.notFoundPost(anchor) + if (this.viewerBlockExists(post.author.did, state)) { + return this.blockedPost(anchor, post.author.did, state) + } + const includedPosts = new Set([anchor]) + const childrenByParentUri: Record = {} + uris.forEach((uri) => { + const post = state.posts?.get(uri) + const parentUri = post?.record.reply?.parent.uri + if (!parentUri) return + if (includedPosts.has(uri)) return + includedPosts.add(uri) + childrenByParentUri[parentUri] ??= [] + childrenByParentUri[parentUri].push(uri) + }) + const rootUri = getRootUri(anchor, postInfo) + const violatesThreadGate = postInfo.violatesThreadGate + + return { + $type: 'app.bsky.feed.defs#threadViewPost', + post, + parent: !violatesThreadGate + ? this.threadParent(anchor, rootUri, state, opts.height) + : undefined, + replies: !violatesThreadGate + ? this.threadReplies( + anchor, + rootUri, + childrenByParentUri, + state, + opts.depth, + ) + : undefined, + } + } + + threadParent( + childUri: string, + rootUri: string, + state: HydrationState, + height: number, + ): ThreadViewPost | NotFoundPost | BlockedPost | undefined { + if (height < 1) return undefined + const parentUri = state.posts?.get(childUri)?.record.reply?.parent.uri + if (!parentUri) return undefined + if (state.postBlocks?.get(childUri)?.reply) { + return this.blockedPost(parentUri, creatorFromUri(parentUri), state) + } + const post = this.post(parentUri, state) + const postInfo = state.posts?.get(parentUri) + if (!postInfo || !post) return this.notFoundPost(parentUri) + if (rootUri !== getRootUri(parentUri, postInfo)) return // outside thread boundary + if (this.viewerBlockExists(post.author.did, state)) { + return this.blockedPost(parentUri, post.author.did, state) + } + return { + $type: 'app.bsky.feed.defs#threadViewPost', + post, + parent: this.threadParent(parentUri, rootUri, state, height - 1), + } + } + + threadReplies( + parentUri: string, + rootUri: string, + childrenByParentUri: Record, + state: HydrationState, + depth: number, + ): (ThreadViewPost | NotFoundPost | BlockedPost)[] | undefined { + if (depth < 1) return undefined + const childrenUris = childrenByParentUri[parentUri] ?? [] + return mapDefined(childrenUris, (uri) => { + const postInfo = state.posts?.get(uri) + if (postInfo?.violatesThreadGate) { + return undefined + } + if (state.postBlocks?.get(uri)?.reply) { + return undefined + } + const post = this.post(uri, state) + if (!postInfo || !post) return this.notFoundPost(uri) + if (rootUri !== getRootUri(uri, postInfo)) return // outside thread boundary + if (this.viewerBlockExists(post.author.did, state)) { + return this.blockedPost(uri, post.author.did, state) + } + return { + $type: 'app.bsky.feed.defs#threadViewPost', + post, + replies: this.threadReplies( + uri, + rootUri, + childrenByParentUri, + state, + depth - 1, + ), + } + }) + } + + // Embeds + // ------------ + + embed( + postUri: string, + embed: Embed | { $type: string }, + state: HydrationState, + depth: number, + ): EmbedView | undefined { + if (isImagesEmbed(embed)) { + return this.imagesEmbed(creatorFromUri(postUri), embed) + } else if (isExternalEmbed(embed)) { + return this.externalEmbed(creatorFromUri(postUri), embed) + } else if (isRecordEmbed(embed)) { + return this.recordEmbed(postUri, embed, state, depth) + } else if (isRecordWithMedia(embed)) { + return this.recordWithMediaEmbed(postUri, embed, state, depth) + } else { + return undefined + } + } + + imagesEmbed(did: string, embed: ImagesEmbed): ImagesEmbedView { + const imgViews = embed.images.map((img) => ({ + thumb: this.imgUriBuilder.getPresetUri( + 'feed_thumbnail', + did, + cidFromBlobJson(img.image), + ), + fullsize: this.imgUriBuilder.getPresetUri( + 'feed_fullsize', + did, + cidFromBlobJson(img.image), + ), + alt: img.alt, + aspectRatio: img.aspectRatio, + })) + return { + $type: 'app.bsky.embed.images#view', + images: imgViews, + } + } + + externalEmbed(did: string, embed: ExternalEmbed): ExternalEmbedView { + const { uri, title, description, thumb } = embed.external + return { + $type: 'app.bsky.embed.external#view', + external: { + uri, + title, + description, + thumb: thumb + ? this.imgUriBuilder.getPresetUri( + 'feed_thumbnail', + did, + cidFromBlobJson(thumb), + ) + : undefined, + }, + } + } + + embedNotFound(uri: string): { $type: string; record: EmbedNotFound } { + return { + $type: 'app.bsky.embed.record#view', + record: { + $type: 'app.bsky.embed.record#viewNotFound', + uri, + notFound: true, + }, + } + } + + embedBlocked( + uri: string, + state: HydrationState, + ): { $type: string; record: EmbedBlocked } { + const creator = creatorFromUri(uri) + return { + $type: 'app.bsky.embed.record#view', + record: { + $type: 'app.bsky.embed.record#viewBlocked', + uri, + blocked: true, + author: { + did: creator, + viewer: this.blockedProfileViewer(creator, state), + }, + }, + } + } + + embedPostView( + uri: string, + state: HydrationState, + depth: number, + ): PostEmbedView | undefined { + const postView = this.post(uri, state, depth) + if (!postView) return + return { + $type: 'app.bsky.embed.record#viewRecord', + uri: postView.uri, + cid: postView.cid, + author: postView.author, + value: postView.record, + labels: postView.labels, + indexedAt: postView.indexedAt, + embeds: depth > 1 ? undefined : postView.embed ? [postView.embed] : [], + } + } + + recordEmbed( + postUri: string, + embed: RecordEmbed, + state: HydrationState, + depth: number, + withTypeTag = true, + ): RecordEmbedView { + const uri = embed.record.uri + const parsedUri = new AtUri(uri) + if ( + this.viewerBlockExists(parsedUri.hostname, state) || + state.postBlocks?.get(postUri)?.embed + ) { + return this.embedBlocked(uri, state) + } + + if (parsedUri.collection === ids.AppBskyFeedPost) { + const view = this.embedPostView(uri, state, depth) + if (!view) return this.embedNotFound(uri) + return this.recordEmbedWrapper(view, withTypeTag) + } else if (parsedUri.collection === ids.AppBskyFeedGenerator) { + const view = this.feedGenerator(uri, state) + if (!view) return this.embedNotFound(uri) + view.$type = 'app.bsky.feed.defs#generatorView' + return this.recordEmbedWrapper(view, withTypeTag) + } else if (parsedUri.collection === ids.AppBskyGraphList) { + const view = this.list(uri, state) + if (!view) return this.embedNotFound(uri) + view.$type = 'app.bsky.graph.defs#listView' + return this.recordEmbedWrapper(view, withTypeTag) + } + return this.embedNotFound(uri) + } + + private recordEmbedWrapper( + record: RecordEmbedViewInternal, + withTypeTag: boolean, + ): RecordEmbedView { + return { + $type: withTypeTag ? 'app.bsky.embed.record#view' : undefined, + record, + } + } + + recordWithMediaEmbed( + postUri: string, + embed: RecordWithMedia, + state: HydrationState, + depth: number, + ): RecordWithMediaView | undefined { + const creator = creatorFromUri(postUri) + let mediaEmbed: ImagesEmbedView | ExternalEmbedView + if (isImagesEmbed(embed.media)) { + mediaEmbed = this.imagesEmbed(creator, embed.media) + } else if (isExternalEmbed(embed.media)) { + mediaEmbed = this.externalEmbed(creator, embed.media) + } else { + return + } + return { + $type: 'app.bsky.embed.recordWithMedia#view', + media: mediaEmbed, + record: this.recordEmbed(postUri, embed.record, state, depth, false), + } + } + + userReplyDisabled(uri: string, state: HydrationState): boolean | undefined { + const post = state.posts?.get(uri) + if (post?.violatesThreadGate) { + return true + } + const rootUriStr: string = post?.record.reply?.root.uri ?? uri + const gate = state.threadgates?.get(postToGateUri(rootUriStr))?.record + if (!gate || !state.viewer) { + return undefined + } + const rootPost = state.posts?.get(rootUriStr)?.record + const ownerDid = new AtUri(rootUriStr).hostname + const { + canReply, + allowFollowing, + allowListUris = [], + } = parseThreadGate(state.viewer, ownerDid, rootPost ?? null, gate) + if (canReply) { + return false + } + if (allowFollowing && state.profileViewers?.get(ownerDid)?.followedBy) { + return false + } + for (const listUri of allowListUris) { + const list = state.listViewers?.get(listUri) + if (list?.viewerInList) { + return false + } + } + return true + } + + notification( + notif: Notification, + lastSeenAt: string | undefined, + state: HydrationState, + ): NotificationView | undefined { + if (!notif.timestamp || !notif.reason) return + const uri = new AtUri(notif.uri) + const authorDid = uri.hostname + const author = this.profile(authorDid, state) + if (!author) return + let recordInfo: RecordInfo> | null | undefined + if (uri.collection === ids.AppBskyFeedPost) { + recordInfo = state.posts?.get(notif.uri) + } else if (uri.collection === ids.AppBskyFeedLike) { + recordInfo = state.likes?.get(notif.uri) + } else if (uri.collection === ids.AppBskyFeedRepost) { + recordInfo = state.reposts?.get(notif.uri) + } else if (uri.collection === ids.AppBskyGraphFollow) { + recordInfo = state.follows?.get(notif.uri) + } + if (!recordInfo) return + const labels = state.labels?.get(notif.uri) ?? [] + const selfLabels = this.selfLabels({ + uri: notif.uri, + cid: recordInfo.cid, + record: recordInfo.record, + }) + const indexedAt = notif.timestamp.toDate().toISOString() + return { + uri: notif.uri, + cid: recordInfo.cid, + author, + reason: notif.reason, + reasonSubject: notif.reasonSubject || undefined, + record: recordInfo.record, + // @NOTE works with a hack in listNotifications so that when there's no last-seen time, + // the user's first notification is marked unread, and all previous read. in this case, + // the last seen time will be equal to the first notification's indexed time. + isRead: lastSeenAt ? lastSeenAt > indexedAt : true, + indexedAt: notif.timestamp.toDate().toISOString(), + labels: [...labels, ...selfLabels], + } + } +} + +const postToGateUri = (uri: string) => { + const aturi = new AtUri(uri) + if (aturi.collection === ids.AppBskyFeedPost) { + aturi.collection = ids.AppBskyFeedThreadgate + } + return aturi.toString() +} + +const getRootUri = (uri: string, post: Post): string => { + return post.record.reply?.root.uri ?? uri +} diff --git a/packages/bsky/src/views/types.ts b/packages/bsky/src/views/types.ts new file mode 100644 index 00000000000..8c5a3deb026 --- /dev/null +++ b/packages/bsky/src/views/types.ts @@ -0,0 +1,72 @@ +import { + Main as ImagesEmbed, + View as ImagesEmbedView, +} from '../lexicon/types/app/bsky/embed/images' +import { + Main as ExternalEmbed, + View as ExternalEmbedView, +} from '../lexicon/types/app/bsky/embed/external' +import { + Main as RecordEmbed, + View as RecordEmbedView, + ViewBlocked as EmbedBlocked, + ViewNotFound as EmbedNotFound, + ViewRecord as PostEmbedView, +} from '../lexicon/types/app/bsky/embed/record' +import { + Main as RecordWithMedia, + View as RecordWithMediaView, +} from '../lexicon/types/app/bsky/embed/recordWithMedia' +import { + BlockedPost, + GeneratorView, + NotFoundPost, + PostView, +} from '../lexicon/types/app/bsky/feed/defs' +import { ListView } from '../lexicon/types/app/bsky/graph/defs' + +export type { + Main as ImagesEmbed, + View as ImagesEmbedView, +} from '../lexicon/types/app/bsky/embed/images' +export { isMain as isImagesEmbed } from '../lexicon/types/app/bsky/embed/images' +export type { + Main as ExternalEmbed, + View as ExternalEmbedView, +} from '../lexicon/types/app/bsky/embed/external' +export { isMain as isExternalEmbed } from '../lexicon/types/app/bsky/embed/external' +export type { + Main as RecordEmbed, + View as RecordEmbedView, + ViewBlocked as EmbedBlocked, + ViewNotFound as EmbedNotFound, + ViewRecord as PostEmbedView, +} from '../lexicon/types/app/bsky/embed/record' +export { isMain as isRecordEmbed } from '../lexicon/types/app/bsky/embed/record' +export type { + Main as RecordWithMedia, + View as RecordWithMediaView, +} from '../lexicon/types/app/bsky/embed/recordWithMedia' +export { isMain as isRecordWithMedia } from '../lexicon/types/app/bsky/embed/recordWithMedia' +export type { View as RecordWithMediaEmbedView } from '../lexicon/types/app/bsky/embed/recordWithMedia' +export type { + BlockedPost, + GeneratorView, + NotFoundPost, + PostView, +} from '../lexicon/types/app/bsky/feed/defs' +export type { ListView } from '../lexicon/types/app/bsky/graph/defs' + +export type { Notification as NotificationView } from '../lexicon/types/app/bsky/notification/listNotifications' + +export type Embed = ImagesEmbed | ExternalEmbed | RecordEmbed | RecordWithMedia + +export type EmbedView = + | ImagesEmbedView + | ExternalEmbedView + | RecordEmbedView + | RecordWithMediaView + +export type MaybePostView = PostView | NotFoundPost | BlockedPost + +export type RecordEmbedViewInternal = PostEmbedView | GeneratorView | ListView diff --git a/packages/bsky/src/views/util.ts b/packages/bsky/src/views/util.ts new file mode 100644 index 00000000000..6f63945ef0a --- /dev/null +++ b/packages/bsky/src/views/util.ts @@ -0,0 +1,64 @@ +import { AtUri } from '@atproto/syntax' +import { BlobRef } from '@atproto/lexicon' +import { Record as PostRecord } from '../lexicon/types/app/bsky/feed/post' +import { + Record as GateRecord, + isFollowingRule, + isListRule, + isMentionRule, +} from '../lexicon/types/app/bsky/feed/threadgate' +import { isMention } from '../lexicon/types/app/bsky/richtext/facet' + +export const creatorFromUri = (uri: string): string => { + return new AtUri(uri).hostname +} + +export const parseThreadGate = ( + replierDid: string, + ownerDid: string, + rootPost: PostRecord | null, + gate: GateRecord | null, +): ParsedThreadGate => { + if (replierDid === ownerDid) { + return { canReply: true } + } + // if gate.allow is unset then *any* reply is allowed, if it is an empty array then *no* reply is allowed + if (!gate || !gate.allow) { + return { canReply: true } + } + + const allowMentions = !!gate.allow.find(isMentionRule) + const allowFollowing = !!gate.allow.find(isFollowingRule) + const allowListUris = gate.allow?.filter(isListRule).map((item) => item.list) + + // check mentions first since it's quick and synchronous + if (allowMentions) { + const isMentioned = rootPost?.facets?.some((facet) => { + return facet.features.some( + (item) => isMention(item) && item.did === replierDid, + ) + }) + if (isMentioned) { + return { canReply: true, allowMentions, allowFollowing, allowListUris } + } + } + return { allowMentions, allowFollowing, allowListUris } +} + +type ParsedThreadGate = { + canReply?: boolean + allowMentions?: boolean + allowFollowing?: boolean + allowListUris?: string[] +} + +export const cidFromBlobJson = (json: BlobRef) => { + if (json instanceof BlobRef) { + return json.ref.toString() + } + // @NOTE below handles the fact that parseRecordBytes() produces raw json rather than lexicon values + if (json['$type'] === 'blob') { + return (json['ref']?.['$link'] ?? '') as string + } + return (json['cid'] ?? '') as string +} diff --git a/packages/bsky/tests/__snapshots__/feed-generation.test.ts.snap b/packages/bsky/tests/__snapshots__/feed-generation.test.ts.snap index 4ac043c9b88..8aea15ddfc8 100644 --- a/packages/bsky/tests/__snapshots__/feed-generation.test.ts.snap +++ b/packages/bsky/tests/__snapshots__/feed-generation.test.ts.snap @@ -65,9 +65,11 @@ Object { "cid": "cids(2)", "creator": Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "description": "its me!", "did": "user(3)", "displayName": "ali", "handle": "alice.test", + "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { "cid": "cids(3)", @@ -938,13 +940,13 @@ Array [ "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(4)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(4)@jpeg", }, Object { "alt": "../dev-env/src/seed/img/key-alt.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(5)@jpeg", }, ], }, @@ -952,8 +954,8 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", - "did": "user(3)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(1)@jpeg", + "did": "user(4)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], @@ -1223,13 +1225,13 @@ Array [ "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(4)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(4)@jpeg", }, Object { "alt": "../dev-env/src/seed/img/key-alt.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(5)@jpeg", }, ], }, @@ -1237,8 +1239,8 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", - "did": "user(3)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(1)@jpeg", + "did": "user(4)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], @@ -1457,9 +1459,11 @@ Object { "cid": "cids(0)", "creator": Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", + "description": "its me!", "did": "user(1)", "displayName": "ali", "handle": "alice.test", + "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { "cid": "cids(2)", @@ -1505,9 +1509,11 @@ Object { "cid": "cids(0)", "creator": Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", + "description": "its me!", "did": "user(1)", "displayName": "ali", "handle": "alice.test", + "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { "cid": "cids(2)", @@ -1533,23 +1539,23 @@ Object { "muted": false, }, }, - "description": "Provides all feed candidates", + "description": "Provides even-indexed feed candidates", "did": "user(0)", - "displayName": "All", + "displayName": "Even", "indexedAt": "1970-01-01T00:00:00.000Z", - "likeCount": 2, + "likeCount": 0, "uri": "record(0)", - "viewer": Object { - "like": "record(4)", - }, + "viewer": Object {}, }, Object { "cid": "cids(3)", "creator": Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", + "description": "its me!", "did": "user(1)", "displayName": "ali", "handle": "alice.test", + "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { "cid": "cids(2)", @@ -1575,13 +1581,15 @@ Object { "muted": false, }, }, - "description": "Provides even-indexed feed candidates", + "description": "Provides all feed candidates", "did": "user(0)", - "displayName": "Even", + "displayName": "All", "indexedAt": "1970-01-01T00:00:00.000Z", - "likeCount": 0, - "uri": "record(5)", - "viewer": Object {}, + "likeCount": 2, + "uri": "record(4)", + "viewer": Object { + "like": "record(5)", + }, }, ], } @@ -1589,14 +1597,17 @@ Object { exports[`feed generation getSuggestedFeeds returns list of suggested feed generators 1`] = ` Object { + "cursor": "4", "feeds": Array [ Object { "cid": "cids(0)", "creator": Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", + "description": "its me!", "did": "user(1)", "displayName": "ali", "handle": "alice.test", + "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { "cid": "cids(2)", @@ -1636,9 +1647,11 @@ Object { "cid": "cids(3)", "creator": Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", + "description": "its me!", "did": "user(1)", "displayName": "ali", "handle": "alice.test", + "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { "cid": "cids(2)", @@ -1676,9 +1689,11 @@ Object { "cid": "cids(4)", "creator": Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", + "description": "its me!", "did": "user(1)", "displayName": "ali", "handle": "alice.test", + "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { "cid": "cids(2)", diff --git a/packages/bsky/tests/_util.ts b/packages/bsky/tests/_util.ts index 8d39a0f9c2c..a86d47a04f6 100644 --- a/packages/bsky/tests/_util.ts +++ b/packages/bsky/tests/_util.ts @@ -49,7 +49,7 @@ export const forSnapshot = (obj: unknown) => { return constantDate } } - if (str.match(/^\d+::bafy/)) { + if (str.match(/^\d+__bafy/)) { return constantKeysetCursor } if (str.match(/\/img\/[^/]+\/.+\/did:plc:[^/]+\/[^/]+@[\w]+$/)) { @@ -110,7 +110,7 @@ export function take( } export const constantDate = new Date(0).toISOString() -export const constantKeysetCursor = '0000000000000::bafycid' +export const constantKeysetCursor = '0000000000000__bafycid' const mapLeafValues = (obj: unknown, fn: (val: unknown) => unknown) => { if (Array.isArray(obj)) { diff --git a/packages/bsky/tests/admin/admin-auth.test.ts b/packages/bsky/tests/admin/admin-auth.test.ts index ff00d0906b0..cb13b58897a 100644 --- a/packages/bsky/tests/admin/admin-auth.test.ts +++ b/packages/bsky/tests/admin/admin-auth.test.ts @@ -27,15 +27,30 @@ describe('admin auth', () => { bskyDid = network.bsky.ctx.cfg.serverDid modServiceKey = await Secp256k1Keypair.create() - const origResolve = network.bsky.ctx.idResolver.did.resolveAtprotoKey - network.bsky.ctx.idResolver.did.resolveAtprotoKey = async ( + const origResolve = network.bsky.dataplane.idResolver.did.resolve + network.bsky.dataplane.idResolver.did.resolve = async function ( did: string, forceRefresh?: boolean, - ) => { + ) { if (did === modServiceDid || did === altModDid) { - return modServiceKey.did() + return { + '@context': [ + 'https://www.w3.org/ns/did/v1', + 'https://w3id.org/security/multikey/v1', + 'https://w3id.org/security/suites/secp256k1-2019/v1', + ], + id: did, + verificationMethod: [ + { + id: `${did}#atproto`, + type: 'Multikey', + controller: did, + publicKeyMultibase: modServiceKey.did().replace('did:key:', ''), + }, + ], + } } - return origResolve(did, forceRefresh) + return origResolve.call(this, did, forceRefresh) } agent = network.bsky.getClient() @@ -70,9 +85,7 @@ describe('admin auth', () => { ) const res = await agent.api.com.atproto.admin.getSubjectStatus( - { - did: repoSubject.did, - }, + { did: repoSubject.did }, headers, ) expect(res.data.subject.did).toBe(repoSubject.did) diff --git a/packages/bsky/tests/admin/moderation.test.ts b/packages/bsky/tests/admin/moderation.test.ts index 6b01bfbbcb6..2f6caa8d17d 100644 --- a/packages/bsky/tests/admin/moderation.test.ts +++ b/packages/bsky/tests/admin/moderation.test.ts @@ -55,18 +55,18 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.bsky.adminAuthHeaders('moderator'), + headers: network.bsky.adminAuthHeaders(), }, ) const res = await agent.api.com.atproto.admin.getSubjectStatus( { did: repoSubject.did, }, - { headers: network.bsky.adminAuthHeaders('moderator') }, + { headers: network.bsky.adminAuthHeaders() }, ) expect(res.data.subject.did).toEqual(sc.dids.bob) expect(res.data.takedown?.applied).toBe(true) - expect(res.data.takedown?.ref).toBe('test-repo') + // expect(res.data.takedown?.ref).toBe('test-repo') @TODO add these checks back in once takedown refs make it into dataplane }) it('restores takendown accounts', async () => { @@ -77,14 +77,14 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.bsky.adminAuthHeaders('moderator'), + headers: network.bsky.adminAuthHeaders(), }, ) const res = await agent.api.com.atproto.admin.getSubjectStatus( { did: repoSubject.did, }, - { headers: network.bsky.adminAuthHeaders('moderator') }, + { headers: network.bsky.adminAuthHeaders() }, ) expect(res.data.subject.did).toEqual(sc.dids.bob) expect(res.data.takedown?.applied).toBe(false) @@ -99,18 +99,18 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.bsky.adminAuthHeaders('moderator'), + headers: network.bsky.adminAuthHeaders(), }, ) const res = await agent.api.com.atproto.admin.getSubjectStatus( { uri: recordSubject.uri, }, - { headers: network.bsky.adminAuthHeaders('moderator') }, + { headers: network.bsky.adminAuthHeaders() }, ) expect(res.data.subject.uri).toEqual(recordSubject.uri) expect(res.data.takedown?.applied).toBe(true) - expect(res.data.takedown?.ref).toBe('test-record') + // expect(res.data.takedown?.ref).toBe('test-record') }) it('restores takendown records', async () => { @@ -121,55 +121,27 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.bsky.adminAuthHeaders('moderator'), + headers: network.bsky.adminAuthHeaders(), }, ) const res = await agent.api.com.atproto.admin.getSubjectStatus( { uri: recordSubject.uri, }, - { headers: network.bsky.adminAuthHeaders('moderator') }, + { headers: network.bsky.adminAuthHeaders() }, ) expect(res.data.subject.uri).toEqual(recordSubject.uri) expect(res.data.takedown?.applied).toBe(false) expect(res.data.takedown?.ref).toBeUndefined() }) - it('does not allow non-full moderators to update subject state', async () => { - const subject = { - $type: 'com.atproto.admin.defs#repoRef', - did: sc.dids.bob, - } - const attemptTakedownTriage = - agent.api.com.atproto.admin.updateSubjectStatus( - { - subject, - takedown: { applied: true }, - }, - { - encoding: 'application/json', - headers: network.bsky.adminAuthHeaders('triage'), - }, - ) - await expect(attemptTakedownTriage).rejects.toThrow( - 'Must be a full moderator to update subject state', - ) - const res = await agent.api.com.atproto.admin.getSubjectStatus( - { - did: subject.did, - }, - { headers: network.bsky.adminAuthHeaders('moderator') }, - ) - expect(res.data.takedown?.applied).toBe(false) - }) - describe('blob takedown', () => { let blobUri: string let imageUri: string beforeAll(async () => { blobUri = `${network.bsky.url}/blob/${blobSubject.did}/${blobSubject.cid}` - imageUri = network.bsky.ctx.imgUriBuilder + imageUri = network.bsky.ctx.views.imgUriBuilder .getPresetUri('feed_thumbnail', blobSubject.did, blobSubject.cid) .replace(network.bsky.ctx.cfg.publicUrl || '', network.bsky.url) // Warm image server cache @@ -194,12 +166,12 @@ describe('moderation', () => { did: blobSubject.did, blob: blobSubject.cid, }, - { headers: network.bsky.adminAuthHeaders('moderator') }, + { headers: network.bsky.adminAuthHeaders() }, ) expect(res.data.subject.did).toEqual(blobSubject.did) expect(res.data.subject.cid).toEqual(blobSubject.cid) expect(res.data.takedown?.applied).toBe(true) - expect(res.data.takedown?.ref).toBe('test-blob') + // expect(res.data.takedown?.ref).toBe('test-blob') }) it('prevents resolution of blob', async () => { @@ -211,12 +183,6 @@ describe('moderation', () => { }) }) - it('prevents image blob from being served, even when cached.', async () => { - const fetchImage = await fetch(imageUri) - expect(fetchImage.status).toEqual(404) - expect(await fetchImage.json()).toEqual({ message: 'Image not found' }) - }) - it('restores blob when takedown is removed', async () => { await agent.api.com.atproto.admin.updateSubjectStatus( { diff --git a/packages/bsky/tests/auth.test.ts b/packages/bsky/tests/auth.test.ts index e08049fa84c..d0903174a2b 100644 --- a/packages/bsky/tests/auth.test.ts +++ b/packages/bsky/tests/auth.test.ts @@ -22,7 +22,8 @@ describe('auth', () => { await network.close() }) - it('handles signing key change for service auth.', async () => { + // @TODO invalidations do not originate from appview frontends: requires identity event on the repo stream. + it.skip('handles signing key change for service auth.', async () => { const issuer = sc.dids.alice const attemptWithKey = async (keypair: Keypair) => { const jwt = await createServiceJwt({ diff --git a/packages/bsky/tests/auto-moderator/fixtures/hiveai_resp_example.json b/packages/bsky/tests/auto-moderator/fixtures/hiveai_resp_example.json deleted file mode 100644 index 2315fa9d0c0..00000000000 --- a/packages/bsky/tests/auto-moderator/fixtures/hiveai_resp_example.json +++ /dev/null @@ -1,401 +0,0 @@ -{ - "id": "02122580-c37f-11ed-81d2-000000000000", - "code": 200, - "project_id": 12345, - "user_id": 12345, - "created_on": "2023-03-15T22:16:18.408Z", - "status": [ - { - "status": { - "code": "0", - "message": "SUCCESS" - }, - "response": { - "input": { - "id": "02122580-c37f-11ed-81d2-000000000000", - "charge": 0.003, - "model": "mod55_dense", - "model_version": 1, - "model_type": "CATEGORIZATION", - "created_on": "2023-03-15T22:16:18.136Z", - "media": { - "url": null, - "filename": "bafkreiam7k6mvkyuoybq4ynhljvj5xa75sdbhjbolzjf5j2udx7vj5gnsy", - "type": "PHOTO", - "mime_type": "jpeg", - "mimetype": "image/jpeg", - "width": 800, - "height": 800, - "num_frames": 1, - "duration": 0 - }, - "user_id": 12345, - "project_id": 12345, - "config_version": 1, - "config_tag": "default" - }, - "output": [ - { - "time": 0, - "classes": [ - { - "class": "general_not_nsfw_not_suggestive", - "score": 0.9998097218132356 - }, - { - "class": "general_nsfw", - "score": 8.857344804177162e-5 - }, - { - "class": "general_suggestive", - "score": 0.00010170473872266839 - }, - { - "class": "no_female_underwear", - "score": 0.9999923079040384 - }, - { - "class": "yes_female_underwear", - "score": 7.692095961599136e-6 - }, - { - "class": "no_male_underwear", - "score": 0.9999984904867634 - }, - { - "class": "yes_male_underwear", - "score": 1.5095132367094679e-6 - }, - { - "class": "no_sex_toy", - "score": 0.9999970970762551 - }, - { - "class": "yes_sex_toy", - "score": 2.9029237450490604e-6 - }, - { - "class": "no_female_nudity", - "score": 0.9999739028909301 - }, - { - "class": "yes_female_nudity", - "score": 2.60971090699536e-5 - }, - { - "class": "no_male_nudity", - "score": 0.9999711373083747 - }, - { - "class": "yes_male_nudity", - "score": 2.8862691625255323e-5 - }, - { - "class": "no_female_swimwear", - "score": 0.9999917609899659 - }, - { - "class": "yes_female_swimwear", - "score": 8.239010034025379e-6 - }, - { - "class": "no_male_shirtless", - "score": 0.9999583350744331 - }, - { - "class": "yes_male_shirtless", - "score": 4.166492556688088e-5 - }, - { - "class": "no_text", - "score": 0.9958378716447616 - }, - { - "class": "text", - "score": 0.0041621283552384265 - }, - { - "class": "animated", - "score": 0.46755478950048235 - }, - { - "class": "hybrid", - "score": 0.0011440363434524984 - }, - { - "class": "natural", - "score": 0.5313011741560651 - }, - { - "class": "animated_gun", - "score": 2.0713000782979496e-5 - }, - { - "class": "gun_in_hand", - "score": 1.5844730446534659e-6 - }, - { - "class": "gun_not_in_hand", - "score": 1.0338973818006654e-6 - }, - { - "class": "no_gun", - "score": 0.9999766686287906 - }, - { - "class": "culinary_knife_in_hand", - "score": 3.8063500083369785e-6 - }, - { - "class": "culinary_knife_not_in_hand", - "score": 7.94057948996249e-7 - }, - { - "class": "knife_in_hand", - "score": 4.5578955723278505e-7 - }, - { - "class": "knife_not_in_hand", - "score": 3.842124714748908e-7 - }, - { - "class": "no_knife", - "score": 0.999994559590014 - }, - { - "class": "a_little_bloody", - "score": 2.1317745626539786e-7 - }, - { - "class": "no_blood", - "score": 0.9999793341236429 - }, - { - "class": "other_blood", - "score": 2.0322054269591763e-5 - }, - { - "class": "very_bloody", - "score": 1.306446309561673e-7 - }, - { - "class": "no_pills", - "score": 0.9999989592376954 - }, - { - "class": "yes_pills", - "score": 1.0407623044588633e-6 - }, - { - "class": "no_smoking", - "score": 0.9999939101969173 - }, - { - "class": "yes_smoking", - "score": 6.089803082758281e-6 - }, - { - "class": "illicit_injectables", - "score": 6.925695592003094e-7 - }, - { - "class": "medical_injectables", - "score": 8.587808234452378e-7 - }, - { - "class": "no_injectables", - "score": 0.9999984486496174 - }, - { - "class": "no_nazi", - "score": 0.9999987449628097 - }, - { - "class": "yes_nazi", - "score": 1.2550371902234279e-6 - }, - { - "class": "no_kkk", - "score": 0.999999762417549 - }, - { - "class": "yes_kkk", - "score": 2.3758245111050425e-7 - }, - { - "class": "no_middle_finger", - "score": 0.9999881515231847 - }, - { - "class": "yes_middle_finger", - "score": 1.184847681536747e-5 - }, - { - "class": "no_terrorist", - "score": 0.9999998870793229 - }, - { - "class": "yes_terrorist", - "score": 1.1292067715380635e-7 - }, - { - "class": "no_overlay_text", - "score": 0.9996453363440359 - }, - { - "class": "yes_overlay_text", - "score": 0.0003546636559640924 - }, - { - "class": "no_sexual_activity", - "score": 0.9999563580374798 - }, - { - "class": "yes_sexual_activity", - "score": 0.99, - "realScore": 4.364196252012032e-5 - }, - { - "class": "hanging", - "score": 3.6435135762510905e-7 - }, - { - "class": "no_hanging_no_noose", - "score": 0.9999980779196416 - }, - { - "class": "noose", - "score": 1.5577290007796094e-6 - }, - { - "class": "no_realistic_nsfw", - "score": 0.9999944341007805 - }, - { - "class": "yes_realistic_nsfw", - "score": 5.565899219571182e-6 - }, - { - "class": "animated_corpse", - "score": 5.276802046755426e-7 - }, - { - "class": "human_corpse", - "score": 2.5449360984211012e-8 - }, - { - "class": "no_corpse", - "score": 0.9999994468704343 - }, - { - "class": "no_self_harm", - "score": 0.9999994515625507 - }, - { - "class": "yes_self_harm", - "score": 5.484374493605692e-7 - }, - { - "class": "no_drawing", - "score": 0.9978276028816608 - }, - { - "class": "yes_drawing", - "score": 0.0021723971183392485 - }, - { - "class": "no_emaciated_body", - "score": 0.9999998146500432 - }, - { - "class": "yes_emaciated_body", - "score": 1.853499568724518e-7 - }, - { - "class": "no_child_present", - "score": 0.9999970498515446 - }, - { - "class": "yes_child_present", - "score": 2.950148455380443e-6 - }, - { - "class": "no_sexual_intent", - "score": 0.9999963861546292 - }, - { - "class": "yes_sexual_intent", - "score": 3.613845370766111e-6 - }, - { - "class": "animal_genitalia_and_human", - "score": 2.255472023465222e-8 - }, - { - "class": "animal_genitalia_only", - "score": 4.6783185199931176e-7 - }, - { - "class": "animated_animal_genitalia", - "score": 6.707857419436447e-7 - }, - { - "class": "no_animal_genitalia", - "score": 0.9999988388276858 - }, - { - "class": "no_gambling", - "score": 0.9999960939687145 - }, - { - "class": "yes_gambling", - "score": 3.906031285604864e-6 - }, - { - "class": "no_undressed", - "score": 0.99999923356218 - }, - { - "class": "yes_undressed", - "score": 7.664378199789045e-7 - }, - { - "class": "no_confederate", - "score": 0.9999925456900376 - }, - { - "class": "yes_confederate", - "score": 7.454309962453175e-6 - }, - { - "class": "animated_alcohol", - "score": 1.8109949948066074e-6 - }, - { - "class": "no_alcohol", - "score": 0.9999916620957963 - }, - { - "class": "yes_alcohol", - "score": 5.88781463445443e-6 - }, - { - "class": "yes_drinking_alcohol", - "score": 6.390945746578106e-7 - }, - { - "class": "no_religious_icon", - "score": 0.9999862158580689 - }, - { - "class": "yes_religious_icon", - "score": 1.3784141931119298e-5 - } - ] - } - ] - } - } - ], - "from_cache": false -} diff --git a/packages/bsky/tests/auto-moderator/hive.test.ts b/packages/bsky/tests/auto-moderator/hive.test.ts deleted file mode 100644 index 3a5cef45a37..00000000000 --- a/packages/bsky/tests/auto-moderator/hive.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import fs from 'fs/promises' -import * as hive from '../../src/auto-moderator/hive' - -describe('labeling', () => { - it('correctly parses hive responses', async () => { - const exampleRespBytes = await fs.readFile( - 'tests/auto-moderator/fixtures/hiveai_resp_example.json', - ) - const exampleResp = JSON.parse(exampleRespBytes.toString()) - const classes = hive.respToClasses(exampleResp) - expect(classes.length).toBeGreaterThan(10) - - const labels = hive.summarizeLabels(classes) - expect(labels).toEqual(['porn']) - }) -}) diff --git a/packages/bsky/tests/auto-moderator/labeler.test.ts b/packages/bsky/tests/auto-moderator/labeler.test.ts deleted file mode 100644 index ceeee8474b2..00000000000 --- a/packages/bsky/tests/auto-moderator/labeler.test.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { TestNetwork, usersSeed } from '@atproto/dev-env' -import { AtUri, BlobRef } from '@atproto/api' -import { Readable } from 'stream' -import { AutoModerator } from '../../src/auto-moderator' -import IndexerContext from '../../src/indexer/context' -import { cidForRecord } from '@atproto/repo' -import { TID } from '@atproto/common' -import { CID } from 'multiformats/cid' -import { ImgLabeler } from '../../src/auto-moderator/hive' -import { TestOzone } from '@atproto/dev-env/src/ozone' - -// outside of test suite so that TestLabeler can access them -let badCid1: CID | undefined = undefined -let badCid2: CID | undefined = undefined - -describe('labeler', () => { - let network: TestNetwork - let ozone: TestOzone - let autoMod: AutoModerator - let ctx: IndexerContext - let badBlob1: BlobRef - let badBlob2: BlobRef - let goodBlob: BlobRef - let alice: string - const postUri = () => AtUri.make(alice, 'app.bsky.feed.post', TID.nextStr()) - - beforeAll(async () => { - network = await TestNetwork.create({ - dbPostgresSchema: 'bsky_labeler', - }) - ozone = network.ozone - ctx = network.bsky.indexer.ctx - const pdsCtx = network.pds.ctx - autoMod = ctx.autoMod - autoMod.imgLabeler = new TestImgLabeler() - const sc = network.getSeedClient() - await usersSeed(sc) - await network.processAll() - alice = sc.dids.alice - const storeBlob = (bytes: Uint8Array) => { - return pdsCtx.actorStore.transact(alice, async (store) => { - const metadata = await store.repo.blob.uploadBlobAndGetMetadata( - 'image/jpeg', - Readable.from([bytes], { objectMode: false }), - ) - const blobRef = await store.repo.blob.trackUntetheredBlob(metadata) - const preparedBlobRef = { - cid: blobRef.ref, - mimeType: 'image/jpeg', - constraints: {}, - } - await store.repo.blob.verifyBlobAndMakePermanent(preparedBlobRef) - await store.repo.blob.associateBlob(preparedBlobRef, postUri()) - return blobRef - }) - } - const bytes1 = new Uint8Array([1, 2, 3, 4]) - const bytes2 = new Uint8Array([5, 6, 7, 8]) - const bytes3 = new Uint8Array([4, 3, 2, 1]) - badBlob1 = await storeBlob(bytes1) - badBlob2 = await storeBlob(bytes2) - goodBlob = await storeBlob(bytes3) - badCid1 = badBlob1.ref - badCid2 = badBlob2.ref - }) - - afterAll(async () => { - await network.close() - }) - - const getLabels = async (subject: string) => { - return ozone.ctx.db.db - .selectFrom('label') - .selectAll() - .where('uri', '=', subject) - .execute() - } - - it('labels text in posts', async () => { - const post = { - $type: 'app.bsky.feed.post', - text: 'blah blah label_me', - createdAt: new Date().toISOString(), - } - const cid = await cidForRecord(post) - const uri = postUri() - autoMod.processRecord(uri, cid, post) - await network.processAll() - const labels = await getLabels(uri.toString()) - expect(labels.length).toBe(1) - expect(labels[0]).toMatchObject({ - src: ozone.ctx.cfg.service.did, - uri: uri.toString(), - cid: cid.toString(), - val: 'test-label', - neg: false, - }) - - // Verify that along with applying the labels, we are also leaving trace of the label as moderation event - // Temporarily assign an instance of moderation service to the autoMod so that we can validate label event - const modSrvc = ozone.ctx.modService(ozone.ctx.db) - const { events } = await modSrvc.getEvents({ - includeAllUserRecords: false, - subject: uri.toString(), - limit: 10, - types: [], - addedLabels: [], - removedLabels: [], - addedTags: [], - removedTags: [], - }) - expect(events.length).toBe(1) - expect(events[0]).toMatchObject({ - action: 'com.atproto.admin.defs#modEventLabel', - subjectUri: uri.toString(), - createLabelVals: 'test-label', - negateLabelVals: null, - comment: `[AutoModerator]: Applying labels`, - createdBy: network.bsky.indexer.ctx.cfg.serverDid, - }) - }) - - it('labels embeds in posts', async () => { - const post = { - $type: 'app.bsky.feed.post', - text: 'blah blah', - embed: { - $type: 'app.bsky.embed.images', - images: [ - { - image: badBlob1, - alt: 'img', - }, - { - image: badBlob2, - alt: 'label_me_2', - }, - { - image: goodBlob, - alt: 'img', - }, - ], - }, - createdAt: new Date().toISOString(), - } - const uri = postUri() - const cid = await cidForRecord(post) - autoMod.processRecord(uri, cid, post) - await autoMod.processAll() - const dbLabels = await getLabels(uri.toString()) - const labels = dbLabels.map((row) => row.val).sort() - expect(labels).toEqual( - ['test-label', 'test-label-2', 'img-label', 'other-img-label'].sort(), - ) - }) -}) - -class TestImgLabeler implements ImgLabeler { - async labelImg(_did: string, cid: CID): Promise { - if (cid.equals(badCid1)) { - return ['img-label'] - } - if (cid.equals(badCid2)) { - return ['other-img-label'] - } - return [] - } -} diff --git a/packages/bsky/tests/blob-resolver.test.ts b/packages/bsky/tests/blob-resolver.test.ts index e428c70ca08..985f347f7c2 100644 --- a/packages/bsky/tests/blob-resolver.test.ts +++ b/packages/bsky/tests/blob-resolver.test.ts @@ -1,6 +1,6 @@ import axios, { AxiosInstance } from 'axios' import { CID } from 'multiformats/cid' -import { verifyCidForBytes } from '@atproto/common' +import { cidForCbor, verifyCidForBytes } from '@atproto/common' import { TestNetwork, basicSeed } from '@atproto/dev-env' import { randomBytes } from '@atproto/crypto' @@ -17,7 +17,6 @@ describe('blob resolver', () => { const sc = network.getSeedClient() await basicSeed(sc) await network.processAll() - await network.bsky.processAll() fileDid = sc.dids.carol fileCid = sc.posts[fileDid][0].images[0].image.ref client = axios.create({ @@ -45,8 +44,9 @@ describe('blob resolver', () => { }) it('404s on missing blob.', async () => { + const badCid = await cidForCbor({ unknown: true }) const { data, status } = await client.get( - `/blob/did:plc:unknown/${fileCid.toString()}`, + `/blob/${fileDid}/${badCid.toString()}`, ) expect(status).toEqual(404) expect(data).toEqual({ @@ -55,6 +55,17 @@ describe('blob resolver', () => { }) }) + it('404s on missing identity.', async () => { + const { data, status } = await client.get( + `/blob/did:plc:unknown/${fileCid.toString()}`, + ) + expect(status).toEqual(404) + expect(data).toEqual({ + error: 'NotFoundError', + message: 'Origin not found', + }) + }) + it('400s on invalid did.', async () => { const { data, status } = await client.get( `/blob/did::/${fileCid.toString()}`, diff --git a/packages/bsky/tests/daemon.test.ts b/packages/bsky/tests/daemon.test.ts deleted file mode 100644 index cb3c7058cff..00000000000 --- a/packages/bsky/tests/daemon.test.ts +++ /dev/null @@ -1,190 +0,0 @@ -import assert from 'assert' -import { AtUri } from '@atproto/api' -import { TestNetwork, usersSeed } from '@atproto/dev-env' -import { BskyDaemon, DaemonConfig, PrimaryDatabase } from '../src' -import { countAll, excluded } from '../src/db/util' -import { NotificationsDaemon } from '../src/daemon/notifications' -import { - BEFORE_LAST_SEEN_DAYS, - BEFORE_LATEST_UNREAD_DAYS, - UNREAD_KEPT_COUNT, -} from '../src/services/util/notification' - -describe('daemon', () => { - let network: TestNetwork - let daemon: BskyDaemon - let db: PrimaryDatabase - let actors: { did: string }[] = [] - - beforeAll(async () => { - network = await TestNetwork.create({ - dbPostgresSchema: 'bsky_daemon', - }) - db = network.bsky.ctx.db.getPrimary() - daemon = BskyDaemon.create({ - db, - cfg: new DaemonConfig({ - version: network.bsky.ctx.cfg.version, - dbPostgresUrl: network.bsky.ctx.cfg.dbPrimaryPostgresUrl, - dbPostgresSchema: network.bsky.ctx.cfg.dbPostgresSchema, - }), - }) - const sc = network.getSeedClient() - await usersSeed(sc) - await network.processAll() - actors = await db.db.selectFrom('actor').selectAll().execute() - }) - - afterAll(async () => { - await network.close() - }) - - describe('notifications daemon', () => { - it('processes all dids', async () => { - for (const { did } of actors) { - await Promise.all([ - setLastSeen(daemon.ctx.db, { did }), - createNotifications(daemon.ctx.db, { - did, - daysAgo: 2 * BEFORE_LAST_SEEN_DAYS, - count: 1, - }), - ]) - } - await expect(countNotifications(db)).resolves.toBe(actors.length) - await runNotifsOnce(daemon.notifications) - await expect(countNotifications(db)).resolves.toBe(0) - }) - - it('removes read notifications older than threshold.', async () => { - const { did } = actors[0] - const lastSeenDaysAgo = 10 - await Promise.all([ - setLastSeen(daemon.ctx.db, { did, daysAgo: lastSeenDaysAgo }), - // read, delete - createNotifications(daemon.ctx.db, { - did, - daysAgo: lastSeenDaysAgo + BEFORE_LAST_SEEN_DAYS + 1, - count: 2, - }), - // read, keep - createNotifications(daemon.ctx.db, { - did, - daysAgo: lastSeenDaysAgo + BEFORE_LAST_SEEN_DAYS - 1, - count: 3, - }), - // unread, keep - createNotifications(daemon.ctx.db, { - did, - daysAgo: lastSeenDaysAgo - 1, - count: 4, - }), - ]) - await expect(countNotifications(db)).resolves.toBe(9) - await runNotifsOnce(daemon.notifications) - await expect(countNotifications(db)).resolves.toBe(7) - await clearNotifications(db) - }) - - it('removes unread notifications older than threshold.', async () => { - const { did } = actors[0] - await Promise.all([ - setLastSeen(daemon.ctx.db, { - did, - daysAgo: 2 * BEFORE_LATEST_UNREAD_DAYS, // all are unread - }), - createNotifications(daemon.ctx.db, { - did, - daysAgo: 0, - count: 1, - }), - createNotifications(daemon.ctx.db, { - did, - daysAgo: BEFORE_LATEST_UNREAD_DAYS - 1, - count: 99, - }), - createNotifications(daemon.ctx.db, { - did, - daysAgo: BEFORE_LATEST_UNREAD_DAYS + 1, - count: 400, - }), - ]) - await expect(countNotifications(db)).resolves.toBe(UNREAD_KEPT_COUNT) - await runNotifsOnce(daemon.notifications) - // none removed when within UNREAD_KEPT_COUNT - await expect(countNotifications(db)).resolves.toBe(UNREAD_KEPT_COUNT) - // add one more, tip over UNREAD_KEPT_COUNT - await createNotifications(daemon.ctx.db, { - did, - daysAgo: BEFORE_LATEST_UNREAD_DAYS + 1, - count: 1, - }) - await runNotifsOnce(daemon.notifications) - // removed all older than BEFORE_LATEST_UNREAD_DAYS - await expect(countNotifications(db)).resolves.toBe(100) - await clearNotifications(db) - }) - }) - - const runNotifsOnce = async (notifsDaemon: NotificationsDaemon) => { - assert(!notifsDaemon.running, 'notifications daemon is already running') - notifsDaemon.run({ forever: false, batchSize: 2 }) - await notifsDaemon.running - } - - const setLastSeen = async ( - db: PrimaryDatabase, - opts: { did: string; daysAgo?: number }, - ) => { - const { did, daysAgo = 0 } = opts - const lastSeenAt = new Date() - lastSeenAt.setDate(lastSeenAt.getDate() - daysAgo) - await db.db - .insertInto('actor_state') - .values({ did, lastSeenNotifs: lastSeenAt.toISOString() }) - .onConflict((oc) => - oc.column('did').doUpdateSet({ - lastSeenNotifs: excluded(db.db, 'lastSeenNotifs'), - }), - ) - .execute() - } - - const createNotifications = async ( - db: PrimaryDatabase, - opts: { - did: string - count: number - daysAgo: number - }, - ) => { - const { did, count, daysAgo } = opts - const sortAt = new Date() - sortAt.setDate(sortAt.getDate() - daysAgo) - await db.db - .insertInto('notification') - .values( - [...Array(count)].map(() => ({ - did, - author: did, - reason: 'none', - recordCid: 'bafycid', - recordUri: AtUri.make(did, 'invalid.collection', 'self').toString(), - sortAt: sortAt.toISOString(), - })), - ) - .execute() - } - - const clearNotifications = async (db: PrimaryDatabase) => { - await db.db.deleteFrom('notification').execute() - } - - const countNotifications = async (db: PrimaryDatabase) => { - const { count } = await db.db - .selectFrom('notification') - .select(countAll.as('count')) - .executeTakeFirstOrThrow() - return count - } -}) diff --git a/packages/bsky/tests/__snapshots__/indexing.test.ts.snap b/packages/bsky/tests/data-plane/__snapshots__/indexing.test.ts.snap similarity index 98% rename from packages/bsky/tests/__snapshots__/indexing.test.ts.snap rename to packages/bsky/tests/data-plane/__snapshots__/indexing.test.ts.snap index 142866aeebd..e927f120505 100644 --- a/packages/bsky/tests/__snapshots__/indexing.test.ts.snap +++ b/packages/bsky/tests/data-plane/__snapshots__/indexing.test.ts.snap @@ -17,7 +17,7 @@ Array [ }, }, Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "feed": Array [ Object { "post": Object { @@ -113,7 +113,7 @@ Array [ "cid": "cids(3)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(3)", "val": "test-label", }, @@ -121,7 +121,7 @@ Array [ "cid": "cids(3)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(3)", "val": "test-label-2", }, @@ -207,7 +207,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(4)", + "did": "user(3)", "handle": "dan.test", "labels": Array [], "viewer": Object { @@ -223,7 +223,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(5)", + "did": "user(4)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -317,7 +317,7 @@ Array [ "cid": "cids(6)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(6)", "val": "test-label", }, @@ -413,10 +413,10 @@ Array [ ], }, Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "follows": Array [ Object { - "did": "user(4)", + "did": "user(3)", "handle": "dan.test", "labels": Array [], "viewer": Object { diff --git a/packages/bsky/tests/db.test.ts b/packages/bsky/tests/data-plane/db.test.ts similarity index 65% rename from packages/bsky/tests/db.test.ts rename to packages/bsky/tests/data-plane/db.test.ts index 28008f9897e..1a095906a98 100644 --- a/packages/bsky/tests/db.test.ts +++ b/packages/bsky/tests/data-plane/db.test.ts @@ -1,20 +1,17 @@ -import { once } from 'events' import { sql } from 'kysely' import { wait } from '@atproto/common' import { TestNetwork } from '@atproto/dev-env' -import { Database } from '../src' -import { PrimaryDatabase } from '../src/db' -import { Leader } from '../src/db/leader' +import { Database } from '../../src' describe('db', () => { let network: TestNetwork - let db: PrimaryDatabase + let db: Database beforeAll(async () => { network = await TestNetwork.create({ dbPostgresSchema: 'bsky_db', }) - db = network.bsky.ctx.db.getPrimary() + db = network.bsky.db }) afterAll(async () => { @@ -189,80 +186,4 @@ describe('db', () => { expect(res.length).toBe(0) }) }) - - describe('Leader', () => { - it('allows leaders to run sequentially.', async () => { - const task = async () => { - await wait(25) - return 'complete' - } - const leader1 = new Leader(707, db) - const leader2 = new Leader(707, db) - const leader3 = new Leader(707, db) - const result1 = await leader1.run(task) - await wait(5) // Short grace period for pg to close session - const result2 = await leader2.run(task) - await wait(5) - const result3 = await leader3.run(task) - await wait(5) - const result4 = await leader3.run(task) - expect([result1, result2, result3, result4]).toEqual([ - { ran: true, result: 'complete' }, - { ran: true, result: 'complete' }, - { ran: true, result: 'complete' }, - { ran: true, result: 'complete' }, - ]) - }) - - it('only allows one leader at a time.', async () => { - const task = async () => { - await wait(75) - return 'complete' - } - const results = await Promise.all([ - new Leader(717, db).run(task), - new Leader(717, db).run(task), - new Leader(717, db).run(task), - ]) - const byRan = (a, b) => Number(a.ran) - Number(b.ran) - expect(results.sort(byRan)).toEqual([ - { ran: false }, - { ran: false }, - { ran: true, result: 'complete' }, - ]) - }) - - it('leaders with different ids do not conflict.', async () => { - const task = async () => { - await wait(75) - return 'complete' - } - const results = await Promise.all([ - new Leader(727, db).run(task), - new Leader(728, db).run(task), - new Leader(729, db).run(task), - ]) - expect(results).toEqual([ - { ran: true, result: 'complete' }, - { ran: true, result: 'complete' }, - { ran: true, result: 'complete' }, - ]) - }) - - it('supports abort.', async () => { - const task = async (ctx: { signal: AbortSignal }) => { - wait(10).then(abort) - return await Promise.race([ - wait(50), - once(ctx.signal, 'abort').then(() => ctx.signal.reason), - ]) - } - const leader = new Leader(737, db) - const abort = () => { - leader.session?.abortController.abort(new Error('Oops!')) - } - const result = await leader.run(task) - expect(result).toEqual({ ran: true, result: new Error('Oops!') }) - }) - }) }) diff --git a/packages/bsky/tests/duplicate-records.test.ts b/packages/bsky/tests/data-plane/duplicate-records.test.ts similarity index 75% rename from packages/bsky/tests/duplicate-records.test.ts rename to packages/bsky/tests/data-plane/duplicate-records.test.ts index 9c7617bd668..da7287893ba 100644 --- a/packages/bsky/tests/duplicate-records.test.ts +++ b/packages/bsky/tests/data-plane/duplicate-records.test.ts @@ -2,23 +2,19 @@ import { AtUri } from '@atproto/syntax' import { cidForCbor, TID } from '@atproto/common' import { WriteOpAction } from '@atproto/repo' import { TestNetwork } from '@atproto/dev-env' -import { Database } from '../src' -import { PrimaryDatabase } from '../src/db' -import * as lex from '../src/lexicon/lexicons' -import { Services } from '../src/indexer/services' +import * as lex from '../../src/lexicon/lexicons' +import { Database } from '../../src' describe('duplicate record', () => { let network: TestNetwork let did: string - let db: PrimaryDatabase - let services: Services + let db: Database beforeAll(async () => { network = await TestNetwork.create({ dbPostgresSchema: 'bsky_duplicates', }) - db = network.bsky.indexer.ctx.db - services = network.bsky.indexer.ctx.services + db = network.bsky.db did = 'did:example:alice' }) @@ -51,21 +47,25 @@ describe('duplicate record', () => { } const uri = AtUri.make(did, coll, TID.nextStr()) const cid = await cidForCbor(repost) - await services - .indexing(db) - .indexRecord(uri, cid, repost, WriteOpAction.Create, repost.createdAt) + await network.bsky.sub.indexingSvc.indexRecord( + uri, + cid, + repost, + WriteOpAction.Create, + repost.createdAt, + ) uris.push(uri) } let count = await countRecords(db, 'repost') expect(count).toBe(1) - await services.indexing(db).deleteRecord(uris[0], false) + await network.bsky.sub.indexingSvc.deleteRecord(uris[0], false) count = await countRecords(db, 'repost') expect(count).toBe(1) - await services.indexing(db).deleteRecord(uris[1], true) + await network.bsky.sub.indexingSvc.deleteRecord(uris[1], true) count = await countRecords(db, 'repost') expect(count).toBe(0) @@ -87,16 +87,20 @@ describe('duplicate record', () => { } const uri = AtUri.make(did, coll, TID.nextStr()) const cid = await cidForCbor(like) - await services - .indexing(db) - .indexRecord(uri, cid, like, WriteOpAction.Create, like.createdAt) + await network.bsky.sub.indexingSvc.indexRecord( + uri, + cid, + like, + WriteOpAction.Create, + like.createdAt, + ) uris.push(uri) } let count = await countRecords(db, 'like') expect(count).toBe(1) - await services.indexing(db).deleteRecord(uris[0], false) + await network.bsky.sub.indexingSvc.deleteRecord(uris[0], false) count = await countRecords(db, 'like') expect(count).toBe(1) @@ -107,7 +111,7 @@ describe('duplicate record', () => { .executeTakeFirst() expect(got?.uri).toEqual(uris[1].toString()) - await services.indexing(db).deleteRecord(uris[1], true) + await network.bsky.sub.indexingSvc.deleteRecord(uris[1], true) count = await countRecords(db, 'like') expect(count).toBe(0) @@ -124,21 +128,25 @@ describe('duplicate record', () => { } const uri = AtUri.make(did, coll, TID.nextStr()) const cid = await cidForCbor(follow) - await services - .indexing(db) - .indexRecord(uri, cid, follow, WriteOpAction.Create, follow.createdAt) + await network.bsky.sub.indexingSvc.indexRecord( + uri, + cid, + follow, + WriteOpAction.Create, + follow.createdAt, + ) uris.push(uri) } let count = await countRecords(db, 'follow') expect(count).toBe(1) - await services.indexing(db).deleteRecord(uris[0], false) + await network.bsky.sub.indexingSvc.deleteRecord(uris[0], false) count = await countRecords(db, 'follow') expect(count).toBe(1) - await services.indexing(db).deleteRecord(uris[1], true) + await network.bsky.sub.indexingSvc.deleteRecord(uris[1], true) count = await countRecords(db, 'follow') expect(count).toBe(0) diff --git a/packages/bsky/tests/handle-invalidation.test.ts b/packages/bsky/tests/data-plane/handle-invalidation.test.ts similarity index 94% rename from packages/bsky/tests/handle-invalidation.test.ts rename to packages/bsky/tests/data-plane/handle-invalidation.test.ts index 70ac7c29a09..8469a8507ef 100644 --- a/packages/bsky/tests/handle-invalidation.test.ts +++ b/packages/bsky/tests/data-plane/handle-invalidation.test.ts @@ -25,8 +25,8 @@ describe('handle invalidation', () => { alice = sc.dids.alice bob = sc.dids.bob - const origResolve = network.bsky.indexer.ctx.idResolver.handle.resolve - network.bsky.indexer.ctx.idResolver.handle.resolve = async ( + const origResolve = network.bsky.dataplane.idResolver.handle.resolve + network.bsky.dataplane.idResolver.handle.resolve = async ( handle: string, ) => { if (mockHandles[handle] === null) { @@ -44,9 +44,8 @@ describe('handle invalidation', () => { const backdateIndexedAt = async (did: string) => { const TWO_DAYS_AGO = new Date(Date.now() - 2 * DAY).toISOString() - await network.bsky.ctx.db - .getPrimary() - .db.updateTable('actor') + await network.bsky.db.db + .updateTable('actor') .set({ indexedAt: TWO_DAYS_AGO }) .where('did', '=', did) .execute() diff --git a/packages/bsky/tests/indexing.test.ts b/packages/bsky/tests/data-plane/indexing.test.ts similarity index 84% rename from packages/bsky/tests/indexing.test.ts rename to packages/bsky/tests/data-plane/indexing.test.ts index 3a5a12b7ac6..f97495c6b32 100644 --- a/packages/bsky/tests/indexing.test.ts +++ b/packages/bsky/tests/data-plane/indexing.test.ts @@ -12,15 +12,16 @@ import AtpAgent, { AppBskyGraphFollow, } from '@atproto/api' import { TestNetwork, SeedClient, usersSeed, basicSeed } from '@atproto/dev-env' -import { forSnapshot } from './_util' -import { ids } from '../src/lexicon/lexicons' -import { Database } from '../src/db' +import { forSnapshot } from '../_util' +import { ids } from '../../src/lexicon/lexicons' +import { Database } from '../../src/data-plane/server/db' describe('indexing', () => { let network: TestNetwork let agent: AtpAgent let pdsAgent: AtpAgent let sc: SeedClient + let db: Database beforeAll(async () => { network = await TestNetwork.create({ @@ -29,12 +30,11 @@ describe('indexing', () => { agent = network.bsky.getClient() pdsAgent = network.pds.getClient() sc = network.getSeedClient() + db = network.bsky.db await usersSeed(sc) // Data in tests is not processed from subscription await network.processAll() - await network.bsky.ingester.sub.destroy() - await network.bsky.indexer.sub.destroy() - await network.bsky.processAll() + await network.bsky.sub.destroy() }) afterAll(async () => { @@ -42,7 +42,6 @@ describe('indexing', () => { }) it('indexes posts.', async () => { - const { db, services } = network.bsky.indexer.ctx const createdAt = new Date().toISOString() const createRecord = await prepareCreate({ did: sc.dids.alice, @@ -93,7 +92,7 @@ describe('indexing', () => { }) // Create - await services.indexing(db).indexRecord(...createRecord) + await network.bsky.sub.indexingSvc.indexRecord(...createRecord) const getAfterCreate = await agent.api.app.bsky.feed.getPostThread( { uri: uri.toString() }, @@ -103,7 +102,7 @@ describe('indexing', () => { const createNotifications = await getNotifications(db, uri) // Update - await services.indexing(db).indexRecord(...updateRecord) + await network.bsky.sub.indexingSvc.indexRecord(...updateRecord) const getAfterUpdate = await agent.api.app.bsky.feed.getPostThread( { uri: uri.toString() }, @@ -113,7 +112,7 @@ describe('indexing', () => { const updateNotifications = await getNotifications(db, uri) // Delete - await services.indexing(db).deleteRecord(...deleteRecord) + await network.bsky.sub.indexingSvc.deleteRecord(...deleteRecord) const getAfterDelete = agent.api.app.bsky.feed.getPostThread( { uri: uri.toString() }, @@ -132,7 +131,6 @@ describe('indexing', () => { }) it('indexes profiles.', async () => { - const { db, services } = network.bsky.indexer.ctx const createRecord = await prepareCreate({ did: sc.dids.dan, collection: ids.AppBskyActorProfile, @@ -159,7 +157,7 @@ describe('indexing', () => { }) // Create - await services.indexing(db).indexRecord(...createRecord) + await network.bsky.sub.indexingSvc.indexRecord(...createRecord) const getAfterCreate = await agent.api.app.bsky.actor.getProfile( { actor: sc.dids.dan }, @@ -168,7 +166,7 @@ describe('indexing', () => { expect(forSnapshot(getAfterCreate.data)).toMatchSnapshot() // Update - await services.indexing(db).indexRecord(...updateRecord) + await network.bsky.sub.indexingSvc.indexRecord(...updateRecord) const getAfterUpdate = await agent.api.app.bsky.actor.getProfile( { actor: sc.dids.dan }, @@ -177,7 +175,7 @@ describe('indexing', () => { expect(forSnapshot(getAfterUpdate.data)).toMatchSnapshot() // Delete - await services.indexing(db).deleteRecord(...deleteRecord) + await network.bsky.sub.indexingSvc.deleteRecord(...deleteRecord) const getAfterDelete = await agent.api.app.bsky.actor.getProfile( { actor: sc.dids.dan }, @@ -187,7 +185,6 @@ describe('indexing', () => { }) it('handles post aggregations out of order.', async () => { - const { db, services } = network.bsky.indexer.ctx const createdAt = new Date().toISOString() const originalPost = await prepareCreate({ did: sc.dids.alice, @@ -234,11 +231,11 @@ describe('indexing', () => { } as AppBskyFeedRepost.Record, }) // reply, like, and repost indexed orior to the original post - await services.indexing(db).indexRecord(...reply) - await services.indexing(db).indexRecord(...like) - await services.indexing(db).indexRecord(...repost) - await services.indexing(db).indexRecord(...originalPost) - await network.bsky.processAll() + await network.bsky.sub.indexingSvc.indexRecord(...reply) + await network.bsky.sub.indexingSvc.indexRecord(...like) + await network.bsky.sub.indexingSvc.indexRecord(...repost) + await network.bsky.sub.indexingSvc.indexRecord(...originalPost) + await network.bsky.sub.background.processAll() const agg = await db.db .selectFrom('post_agg') .selectAll() @@ -258,14 +255,13 @@ describe('indexing', () => { rkey: uri.rkey, }) } - await services.indexing(db).deleteRecord(...del(reply[0])) - await services.indexing(db).deleteRecord(...del(like[0])) - await services.indexing(db).deleteRecord(...del(repost[0])) - await services.indexing(db).deleteRecord(...del(originalPost[0])) + await network.bsky.sub.indexingSvc.deleteRecord(...del(reply[0])) + await network.bsky.sub.indexingSvc.deleteRecord(...del(like[0])) + await network.bsky.sub.indexingSvc.deleteRecord(...del(repost[0])) + await network.bsky.sub.indexingSvc.deleteRecord(...del(originalPost[0])) }) it('does not notify user of own like or repost', async () => { - const { db, services } = network.bsky.indexer.ctx const createdAt = new Date().toISOString() const originalPost = await prepareCreate({ @@ -323,13 +319,12 @@ describe('indexing', () => { } as AppBskyFeedRepost.Record, }) - await services.indexing(db).indexRecord(...originalPost) - await services.indexing(db).indexRecord(...ownLike) - await services.indexing(db).indexRecord(...ownRepost) - await services.indexing(db).indexRecord(...aliceLike) - await services.indexing(db).indexRecord(...aliceRepost) - - await network.bsky.processAll() + await network.bsky.sub.indexingSvc.indexRecord(...originalPost) + await network.bsky.sub.indexingSvc.indexRecord(...ownLike) + await network.bsky.sub.indexingSvc.indexRecord(...ownRepost) + await network.bsky.sub.indexingSvc.indexRecord(...aliceLike) + await network.bsky.sub.indexingSvc.indexRecord(...aliceRepost) + await network.bsky.sub.background.processAll() const { data: { notifications }, @@ -354,15 +349,14 @@ describe('indexing', () => { }) } - await services.indexing(db).deleteRecord(...del(ownLike[0])) - await services.indexing(db).deleteRecord(...del(ownRepost[0])) - await services.indexing(db).deleteRecord(...del(aliceLike[0])) - await services.indexing(db).deleteRecord(...del(aliceRepost[0])) - await services.indexing(db).deleteRecord(...del(originalPost[0])) + await network.bsky.sub.indexingSvc.deleteRecord(...del(ownLike[0])) + await network.bsky.sub.indexingSvc.deleteRecord(...del(ownRepost[0])) + await network.bsky.sub.indexingSvc.deleteRecord(...del(aliceLike[0])) + await network.bsky.sub.indexingSvc.deleteRecord(...del(aliceRepost[0])) + await network.bsky.sub.indexingSvc.deleteRecord(...del(originalPost[0])) }) it('handles profile aggregations out of order.', async () => { - const { db, services } = network.bsky.indexer.ctx const createdAt = new Date().toISOString() const unknownDid = 'did:example:unknown' const follow = await prepareCreate({ @@ -374,8 +368,8 @@ describe('indexing', () => { createdAt, } as AppBskyGraphFollow.Record, }) - await services.indexing(db).indexRecord(...follow) - await network.bsky.processAll() + await network.bsky.sub.indexingSvc.indexRecord(...follow) + await network.bsky.sub.background.processAll() const agg = await db.db .selectFrom('profile_agg') .select(['did', 'followersCount']) @@ -393,22 +387,19 @@ describe('indexing', () => { rkey: uri.rkey, }) } - await services.indexing(db).deleteRecord(...del(follow[0])) + await network.bsky.sub.indexingSvc.deleteRecord(...del(follow[0])) }) describe('indexRepo', () => { beforeAll(async () => { - network.bsky.indexer.sub.resume() - network.bsky.ingester.sub.resume() + network.bsky.sub.run() await basicSeed(sc, false) await network.processAll() - await network.bsky.ingester.sub.destroy() - await network.bsky.indexer.sub.destroy() - await network.bsky.processAll() + await network.bsky.sub.destroy() + await network.bsky.sub.background.processAll() }) it('preserves indexes when no record changes.', async () => { - const { db, services } = network.bsky.indexer.ctx // Mark originals const { data: origProfile } = await agent.api.app.bsky.actor.getProfile( { actor: sc.dids.alice }, @@ -427,8 +418,8 @@ describe('indexing', () => { await pdsAgent.api.com.atproto.sync.getLatestCommit({ did: sc.dids.alice, }) - await services.indexing(db).indexRepo(sc.dids.alice, commit.cid) - await network.bsky.processAll() + await network.bsky.sub.indexingSvc.indexRepo(sc.dids.alice, commit.cid) + await network.bsky.sub.background.processAll() // Check const { data: profile } = await agent.api.app.bsky.actor.getProfile( { actor: sc.dids.alice }, @@ -448,7 +439,6 @@ describe('indexing', () => { }) it('updates indexes when records change.', async () => { - const { db, services } = network.bsky.indexer.ctx // Update profile await pdsAgent.api.com.atproto.repo.putRecord( { @@ -472,8 +462,8 @@ describe('indexing', () => { await pdsAgent.api.com.atproto.sync.getLatestCommit({ did: sc.dids.alice, }) - await services.indexing(db).indexRepo(sc.dids.alice, commit.cid) - await network.bsky.processAll() + await network.bsky.sub.indexingSvc.indexRepo(sc.dids.alice, commit.cid) + await network.bsky.sub.background.processAll() // Check const { data: profile } = await agent.api.app.bsky.actor.getProfile( { actor: sc.dids.alice }, @@ -495,7 +485,6 @@ describe('indexing', () => { }) it('skips invalid records.', async () => { - const { db, services } = network.bsky.indexer.ctx const { accountManager } = network.pds.ctx // const { db: pdsDb, services: pdsServices } = network.pds.ctx // Create a good and a bad post record @@ -531,7 +520,7 @@ describe('indexing', () => { await pdsAgent.api.com.atproto.sync.getLatestCommit({ did: sc.dids.alice, }) - await services.indexing(db).indexRepo(sc.dids.alice, commit.cid) + await network.bsky.sub.indexingSvc.indexRepo(sc.dids.alice, commit.cid) // Check const getGoodPost = agent.api.app.bsky.feed.getPostThread( { uri: writes[0].uri.toString(), depth: 0 }, @@ -556,7 +545,6 @@ describe('indexing', () => { } it('indexes handle for a fresh did', async () => { - const { db, services } = network.bsky.indexer.ctx const now = new Date().toISOString() const sessionAgent = new AtpAgent({ service: network.pds.url }) const { @@ -567,12 +555,11 @@ describe('indexing', () => { password: 'password', }) await expect(getIndexedHandle(did)).rejects.toThrow('Profile not found') - await services.indexing(db).indexHandle(did, now) + await network.bsky.sub.indexingSvc.indexHandle(did, now) await expect(getIndexedHandle(did)).resolves.toEqual('did1.test') }) it('reindexes handle for existing did when forced', async () => { - const { db, services } = network.bsky.indexer.ctx const now = new Date().toISOString() const sessionAgent = new AtpAgent({ service: network.pds.url }) const { @@ -582,19 +569,18 @@ describe('indexing', () => { handle: 'did2.test', password: 'password', }) - await services.indexing(db).indexHandle(did, now) + await network.bsky.sub.indexingSvc.indexHandle(did, now) await expect(getIndexedHandle(did)).resolves.toEqual('did2.test') await sessionAgent.com.atproto.identity.updateHandle({ handle: 'did2-updated.test', }) - await services.indexing(db).indexHandle(did, now) + await network.bsky.sub.indexingSvc.indexHandle(did, now) await expect(getIndexedHandle(did)).resolves.toEqual('did2.test') // Didn't update, not forced - await services.indexing(db).indexHandle(did, now, true) + await network.bsky.sub.indexingSvc.indexHandle(did, now, true) await expect(getIndexedHandle(did)).resolves.toEqual('did2-updated.test') }) it('handles profile aggregations out of order', async () => { - const { db, services } = network.bsky.indexer.ctx const now = new Date().toISOString() const sessionAgent = new AtpAgent({ service: network.pds.url }) const { @@ -613,9 +599,9 @@ describe('indexing', () => { createdAt: now, } as AppBskyGraphFollow.Record, }) - await services.indexing(db).indexRecord(...follow) - await services.indexing(db).indexHandle(did, now) - await network.bsky.processAll() + await network.bsky.sub.indexingSvc.indexRecord(...follow) + await network.bsky.sub.indexingSvc.indexHandle(did, now) + await network.bsky.sub.background.processAll() const agg = await db.db .selectFrom('profile_agg') .select(['did', 'followersCount']) @@ -630,13 +616,12 @@ describe('indexing', () => { describe('tombstoneActor', () => { it('does not unindex actor when they are still being hosted by their pds', async () => { - const { db, services } = network.bsky.indexer.ctx const { data: profileBefore } = await agent.api.app.bsky.actor.getProfile( { actor: sc.dids.alice }, { headers: await network.serviceHeaders(sc.dids.bob) }, ) // Attempt indexing tombstone - await services.indexing(db).tombstoneActor(sc.dids.alice) + await network.bsky.sub.indexingSvc.tombstoneActor(sc.dids.alice) const { data: profileAfter } = await agent.api.app.bsky.actor.getProfile( { actor: sc.dids.alice }, { headers: await network.serviceHeaders(sc.dids.bob) }, @@ -645,7 +630,6 @@ describe('indexing', () => { }) it('unindexes actor when they are no longer hosted by their pds', async () => { - const { db, services } = network.bsky.indexer.ctx const { alice } = sc.dids const getProfileBefore = agent.api.app.bsky.actor.getProfile( { actor: alice }, @@ -664,7 +648,7 @@ describe('indexing', () => { }) await network.pds.ctx.backgroundQueue.processAll() // Index tombstone - await services.indexing(db).tombstoneActor(alice) + await network.bsky.sub.indexingSvc.tombstoneActor(alice) const getProfileAfter = agent.api.app.bsky.actor.getProfile( { actor: alice }, { headers: await network.serviceHeaders(sc.dids.bob) }, diff --git a/packages/bsky/tests/subscription/repo.test.ts b/packages/bsky/tests/data-plane/subscription/repo.test.ts similarity index 83% rename from packages/bsky/tests/subscription/repo.test.ts rename to packages/bsky/tests/data-plane/subscription/repo.test.ts index fe910c85603..3a02b2fa61c 100644 --- a/packages/bsky/tests/subscription/repo.test.ts +++ b/packages/bsky/tests/data-plane/subscription/repo.test.ts @@ -4,14 +4,13 @@ import { CommitData } from '@atproto/repo' import { PreparedWrite } from '@atproto/pds/src/repo' import * as sequencer from '@atproto/pds/src/sequencer' import { cborDecode, cborEncode } from '@atproto/common' -import { DatabaseSchemaType } from '../../src/db/database-schema' -import { ids } from '../../src/lexicon/lexicons' -import { forSnapshot } from '../_util' -import { AppContext, Database } from '../../src' +import { DatabaseSchemaType } from '../../../src/data-plane/server/db/database-schema' +import { ids } from '../../../src/lexicon/lexicons' +import { forSnapshot } from '../../_util' +import { Database } from '../../../src' describe('sync', () => { let network: TestNetwork - let ctx: AppContext let pdsAgent: AtpAgent let sc: SeedClient @@ -19,7 +18,6 @@ describe('sync', () => { network = await TestNetwork.create({ dbPostgresSchema: 'bsky_subscription_repo', }) - ctx = network.bsky.ctx pdsAgent = network.pds.getClient() sc = network.getSeedClient() await basicSeed(sc) @@ -30,7 +28,7 @@ describe('sync', () => { }) it('indexes permit history being replayed.', async () => { - const db = ctx.db.getPrimary() + const { db } = network.bsky // Generate some modifications and dupes const { alice, bob, carol, dan } = sc.dids @@ -62,16 +60,12 @@ describe('sync', () => { const originalTableDump = await getTableDump() // Reprocess repos via sync subscription, on top of existing indices - await network.bsky.ingester.sub.destroy() - await network.bsky.indexer.sub.destroy() - // Hard reset of state in redis - await network.bsky.ingester.sub.resetCursor() - const indexerSub = network.bsky.indexer.sub - const partition = indexerSub.partitions.get(0) - await network.bsky.indexer.ctx.redis.del(partition.key) + await network.bsky.sub.destroy() + // Hard reset of state + network.bsky.sub.cursor = 0 + network.bsky.sub.seenSeq = null // Boot streams back up - network.bsky.indexer.sub.resume() - network.bsky.ingester.sub.resume() + network.bsky.sub.run() await network.processAll() // Permissive of indexedAt times changing @@ -102,7 +96,7 @@ describe('sync', () => { }) await network.processAll() // confirm jack was indexed as an actor despite the bad event - const actors = await dumpTable(ctx.db.getPrimary(), 'actor', ['did']) + const actors = await dumpTable(network.bsky.db, 'actor', ['did']) expect(actors.map((a) => a.handle)).toContain('jack.test') network.pds.ctx.sequencer.sequenceCommit = sequenceCommitOrig }) diff --git a/packages/bsky/tests/subscription/util.test.ts b/packages/bsky/tests/data-plane/subscription/util.test.ts similarity index 98% rename from packages/bsky/tests/subscription/util.test.ts rename to packages/bsky/tests/data-plane/subscription/util.test.ts index 497532f643b..0aba097c334 100644 --- a/packages/bsky/tests/subscription/util.test.ts +++ b/packages/bsky/tests/data-plane/subscription/util.test.ts @@ -3,8 +3,8 @@ import { ConsecutiveList, LatestQueue, PartitionedQueue, -} from '../../src/subscription/util' -import { randomStr } from '../../../crypto/src' +} from '../../../src/data-plane/server/subscription/util' +import { randomStr } from '../../../../crypto/src' describe('subscription utils', () => { describe('ConsecutiveList', () => { diff --git a/packages/bsky/tests/did-cache.test.ts b/packages/bsky/tests/did-cache.test.ts deleted file mode 100644 index 20114779fff..00000000000 --- a/packages/bsky/tests/did-cache.test.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { TestNetwork, SeedClient, usersSeed } from '@atproto/dev-env' -import { IdResolver } from '@atproto/identity' -import DidRedisCache from '../src/did-cache' -import { wait } from '@atproto/common' -import { Redis } from '../src' - -describe('did cache', () => { - let network: TestNetwork - let sc: SeedClient - let idResolver: IdResolver - let redis: Redis - let didCache: DidRedisCache - - let alice: string - let bob: string - let carol: string - let dan: string - - beforeAll(async () => { - network = await TestNetwork.create({ - dbPostgresSchema: 'bsky_did_cache', - }) - idResolver = network.bsky.indexer.ctx.idResolver - redis = network.bsky.indexer.ctx.redis - didCache = network.bsky.indexer.ctx.didCache - sc = network.getSeedClient() - await usersSeed(sc) - await network.processAll() - alice = sc.dids.alice - bob = sc.dids.bob - carol = sc.dids.carol - dan = sc.dids.dan - }) - - afterAll(async () => { - await network.close() - }) - - it('caches dids on lookup', async () => { - await didCache.processAll() - const docs = await Promise.all([ - idResolver.did.cache?.checkCache(alice), - idResolver.did.cache?.checkCache(bob), - idResolver.did.cache?.checkCache(carol), - idResolver.did.cache?.checkCache(dan), - ]) - expect(docs.length).toBe(4) - expect(docs[0]?.doc.id).toEqual(alice) - expect(docs[1]?.doc.id).toEqual(bob) - expect(docs[2]?.doc.id).toEqual(carol) - expect(docs[3]?.doc.id).toEqual(dan) - }) - - it('clears cache and repopulates', async () => { - await Promise.all([ - idResolver.did.cache?.clearEntry(alice), - idResolver.did.cache?.clearEntry(bob), - idResolver.did.cache?.clearEntry(carol), - idResolver.did.cache?.clearEntry(dan), - ]) - const docsCleared = await Promise.all([ - idResolver.did.cache?.checkCache(alice), - idResolver.did.cache?.checkCache(bob), - idResolver.did.cache?.checkCache(carol), - idResolver.did.cache?.checkCache(dan), - ]) - expect(docsCleared).toEqual([null, null, null, null]) - - await Promise.all([ - idResolver.did.resolve(alice), - idResolver.did.resolve(bob), - idResolver.did.resolve(carol), - idResolver.did.resolve(dan), - ]) - await didCache.processAll() - - const docs = await Promise.all([ - idResolver.did.cache?.checkCache(alice), - idResolver.did.cache?.checkCache(bob), - idResolver.did.cache?.checkCache(carol), - idResolver.did.cache?.checkCache(dan), - ]) - expect(docs.length).toBe(4) - expect(docs[0]?.doc.id).toEqual(alice) - expect(docs[1]?.doc.id).toEqual(bob) - expect(docs[2]?.doc.id).toEqual(carol) - expect(docs[3]?.doc.id).toEqual(dan) - }) - - it('accurately reports expired dids & refreshes the cache', async () => { - const didCache = new DidRedisCache(redis.withNamespace('did-doc'), { - staleTTL: 1, - maxTTL: 60000, - }) - const shortCacheResolver = new IdResolver({ - plcUrl: network.bsky.ctx.cfg.didPlcUrl, - didCache, - }) - const doc = await shortCacheResolver.did.resolve(alice) - await didCache.processAll() - // let's mess with alice's doc so we know what we're getting - await didCache.cacheDid(alice, { ...doc, id: 'did:example:alice' }) - await wait(5) - - // first check the cache & see that we have the stale value - const cached = await shortCacheResolver.did.cache?.checkCache(alice) - expect(cached?.stale).toBe(true) - expect(cached?.doc.id).toEqual('did:example:alice') - // see that the resolver gives us the stale value while it revalidates - const staleGet = await shortCacheResolver.did.resolve(alice) - expect(staleGet?.id).toEqual('did:example:alice') - await didCache.processAll() - - // since it revalidated, ensure we have the new value - const updatedCache = await shortCacheResolver.did.cache?.checkCache(alice) - expect(updatedCache?.doc.id).toEqual(alice) - const updatedGet = await shortCacheResolver.did.resolve(alice) - expect(updatedGet?.id).toEqual(alice) - await didCache.destroy() - }) - - it('does not return expired dids & refreshes the cache', async () => { - const didCache = new DidRedisCache(redis.withNamespace('did-doc'), { - staleTTL: 0, - maxTTL: 1, - }) - const shortExpireResolver = new IdResolver({ - plcUrl: network.bsky.ctx.cfg.didPlcUrl, - didCache, - }) - const doc = await shortExpireResolver.did.resolve(alice) - await didCache.processAll() - - // again, we mess with the cached doc so we get something different - await didCache.cacheDid(alice, { ...doc, id: 'did:example:alice' }) - await wait(5) - - // see that the resolver does not return expired value & instead force refreshes - const staleGet = await shortExpireResolver.did.resolve(alice) - expect(staleGet?.id).toEqual(alice) - await didCache.destroy() - }) -}) diff --git a/packages/bsky/tests/feed-generation.test.ts b/packages/bsky/tests/feed-generation.test.ts index 5e63025ff7b..44f3760808b 100644 --- a/packages/bsky/tests/feed-generation.test.ts +++ b/packages/bsky/tests/feed-generation.test.ts @@ -1,3 +1,6 @@ +import assert from 'assert' +import { XRPCError } from '@atproto/xrpc' +import { AuthRequiredError } from '@atproto/xrpc-server' import { TID } from '@atproto/common' import { AtUri, AtpAgent } from '@atproto/api' import { @@ -16,9 +19,6 @@ import { SkeletonFeedPost, } from '../src/lexicon/types/app/bsky/feed/defs' import { forSnapshot, paginateAll } from './_util' -import { AuthRequiredError } from '@atproto/xrpc-server' -import assert from 'assert' -import { XRPCError } from '@atproto/xrpc' describe('feed generation', () => { let network: TestNetwork @@ -74,9 +74,8 @@ describe('feed generation', () => { { uri: feedUriBadPagination.toString(), order: 3 }, { uri: primeUri.toString(), order: 4 }, ] - await network.bsky.ctx.db - .getPrimary() - .db.insertInto('suggested_feed') + await network.bsky.db.db + .insertInto('suggested_feed') .values(feedSuggestions) .execute() }) @@ -151,23 +150,10 @@ describe('feed generation', () => { sc.getHeaders(alice), ) await network.processAll() - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.repo.strongRef', - uri: prime.uri, - cid: prime.cid, - }, - takedown: { - applied: true, - ref: 'test', - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.takedownRecord({ + recordUri: prime.uri, + }) + feedUriAll = all.uri feedUriAllRef = new RecordRef(all.uri, all.cid) feedUriEven = even.uri @@ -319,7 +305,6 @@ describe('feed generation', () => { sc.getHeaders(sc.dids.bob), ) await network.processAll() - await network.bsky.processAll() // now take it offline await bobFg.close() @@ -359,15 +344,24 @@ describe('feed generation', () => { describe('getPopularFeedGenerators', () => { it('gets popular feed generators', async () => { - const resEven = - await agent.api.app.bsky.unspecced.getPopularFeedGenerators( - {}, - { headers: await network.serviceHeaders(sc.dids.bob) }, - ) - expect(resEven.data.feeds.map((f) => f.likeCount)).toEqual([ - 2, 0, 0, 0, 0, + const res = await agent.api.app.bsky.unspecced.getPopularFeedGenerators( + {}, + { headers: await network.serviceHeaders(sc.dids.bob) }, + ) + expect(res.data.feeds.map((f) => f.uri)).not.toContain(feedUriPrime) // taken-down + expect(res.data.feeds.map((f) => f.uri)).toEqual([ + feedUriAll, + feedUriEven, + feedUriBadPagination, ]) - expect(resEven.data.feeds.map((f) => f.uri)).not.toContain(feedUriPrime) // taken-down + }) + + it('searches feed generators', async () => { + const res = await agent.api.app.bsky.unspecced.getPopularFeedGenerators( + { query: 'all' }, + { headers: await network.serviceHeaders(sc.dids.bob) }, + ) + expect(res.data.feeds.map((f) => f.uri)).toEqual([feedUriAll]) }) it('paginates', async () => { @@ -376,7 +370,6 @@ describe('feed generation', () => { {}, { headers: await network.serviceHeaders(sc.dids.bob) }, ) - const resOne = await agent.api.app.bsky.unspecced.getPopularFeedGenerators( { limit: 2 }, diff --git a/packages/bsky/tests/image/server.test.ts b/packages/bsky/tests/image/server.test.ts index ee4d668945d..5ccd3dbd70a 100644 --- a/packages/bsky/tests/image/server.test.ts +++ b/packages/bsky/tests/image/server.test.ts @@ -18,7 +18,6 @@ describe('image processing server', () => { const sc = network.getSeedClient() await basicSeed(sc) await network.processAll() - await network.bsky.processAll() fileDid = sc.dids.carol fileCid = sc.posts[fileDid][0].images[0].image.ref client = axios.create({ diff --git a/packages/bsky/tests/image/uri.test.ts b/packages/bsky/tests/image/uri.test.ts index 60586d23f6b..137e2359706 100644 --- a/packages/bsky/tests/image/uri.test.ts +++ b/packages/bsky/tests/image/uri.test.ts @@ -14,16 +14,20 @@ describe('image uri builder', () => { }) it('generates paths.', () => { - expect(ImageUriBuilder.getPath({ preset: 'banner', did, cid })).toEqual( - `/banner/plain/${did}/${cid.toString()}@jpeg`, - ) expect( - ImageUriBuilder.getPath({ preset: 'feed_thumbnail', did, cid }), + ImageUriBuilder.getPath({ preset: 'banner', did, cid: cid.toString() }), + ).toEqual(`/banner/plain/${did}/${cid.toString()}@jpeg`) + expect( + ImageUriBuilder.getPath({ + preset: 'feed_thumbnail', + did, + cid: cid.toString(), + }), ).toEqual(`/feed_thumbnail/plain/${did}/${cid.toString()}@jpeg`) }) it('generates uris.', () => { - expect(uriBuilder.getPresetUri('banner', did, cid)).toEqual( + expect(uriBuilder.getPresetUri('banner', did, cid.toString())).toEqual( `https://example.com/img/banner/plain/${did}/${cid.toString()}@jpeg`, ) expect( @@ -38,7 +42,7 @@ describe('image uri builder', () => { ImageUriBuilder.getOptions(`/banner/plain/${did}/${cid.toString()}@png`), ).toEqual({ did: 'did:plc:xyz', - cid, + cid: cid.toString(), fit: 'cover', format: 'png', height: 1000, @@ -52,7 +56,7 @@ describe('image uri builder', () => { ), ).toEqual({ did: 'did:plc:xyz', - cid, + cid: cid.toString(), fit: 'inside', format: 'jpeg', height: 2000, diff --git a/packages/bsky/tests/notification-server.test.ts b/packages/bsky/tests/notification-server.test.ts deleted file mode 100644 index 0efd1e448b4..00000000000 --- a/packages/bsky/tests/notification-server.test.ts +++ /dev/null @@ -1,270 +0,0 @@ -import AtpAgent, { AtUri } from '@atproto/api' -import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' -import { - CourierNotificationServer, - GorushNotificationServer, -} from '../src/notifications' -import { Database } from '../src' -import { createCourierClient } from '../src/courier' - -describe('notification server', () => { - let network: TestNetwork - let agent: AtpAgent - let pdsAgent: AtpAgent - let sc: SeedClient - let notifServer: GorushNotificationServer - - // account dids, for convenience - let alice: string - - beforeAll(async () => { - network = await TestNetwork.create({ - dbPostgresSchema: 'bsky_notification_server', - }) - agent = network.bsky.getClient() - pdsAgent = network.pds.getClient() - sc = network.getSeedClient() - await basicSeed(sc) - await network.processAll() - await network.bsky.processAll() - alice = sc.dids.alice - notifServer = new GorushNotificationServer( - network.bsky.ctx.db.getPrimary(), - 'http://mock', - ) - }) - - afterAll(async () => { - await network.close() - }) - - describe('registerPush', () => { - it('registers push notification token and device.', async () => { - const res = await agent.api.app.bsky.notification.registerPush( - { - serviceDid: network.bsky.ctx.cfg.serverDid, - platform: 'ios', - token: '123', - appId: 'xyz.blueskyweb.app', - }, - { - encoding: 'application/json', - headers: await network.serviceHeaders(alice), - }, - ) - expect(res.success).toEqual(true) - }) - - it('allows reregistering push notification token.', async () => { - const res1 = await agent.api.app.bsky.notification.registerPush( - { - serviceDid: network.bsky.ctx.cfg.serverDid, - platform: 'web', - token: '234', - appId: 'xyz.blueskyweb.app', - }, - { - encoding: 'application/json', - headers: await network.serviceHeaders(alice), - }, - ) - const res2 = await agent.api.app.bsky.notification.registerPush( - { - serviceDid: network.bsky.ctx.cfg.serverDid, - platform: 'web', - token: '234', - appId: 'xyz.blueskyweb.app', - }, - { - encoding: 'application/json', - headers: await network.serviceHeaders(alice), - }, - ) - expect(res1.success).toEqual(true) - expect(res2.success).toEqual(true) - }) - - it('does not allows registering push notification at mismatching service.', async () => { - const tryRegister = agent.api.app.bsky.notification.registerPush( - { - serviceDid: 'did:web:notifservice.com', - platform: 'ios', - token: '123', - appId: 'xyz.blueskyweb.app', - }, - { - encoding: 'application/json', - headers: await network.serviceHeaders(alice), - }, - ) - await expect(tryRegister).rejects.toThrow('Invalid serviceDid.') - }) - }) - - describe('NotificationServer', () => { - it('gets notification display attributes: title and body', async () => { - const db = network.bsky.ctx.db.getPrimary() - const notif = await getLikeNotification(db, alice) - if (!notif) throw new Error('no notification found') - const views = await notifServer.getNotificationViews([notif]) - if (!views.length) - throw new Error('no notification display attributes found') - expect(views[0].title).toEqual('bobby liked your post') - }) - - it('filters notifications that violate blocks', async () => { - const db = network.bsky.ctx.db.getPrimary() - const notif = await getLikeNotification(db, alice) - if (!notif) throw new Error('no notification found') - const blockRef = await pdsAgent.api.app.bsky.graph.block.create( - { repo: alice }, - { subject: notif.author, createdAt: new Date().toISOString() }, - sc.getHeaders(alice), - ) - await network.processAll() - // verify inverse of block - const flippedNotif = { - ...notif, - did: notif.author, - author: notif.did, - } - const views = await notifServer.getNotificationViews([ - notif, - flippedNotif, - ]) - expect(views.length).toBe(0) - const uri = new AtUri(blockRef.uri) - await pdsAgent.api.app.bsky.graph.block.delete( - { repo: alice, rkey: uri.rkey }, - sc.getHeaders(alice), - ) - await network.processAll() - }) - - it('filters notifications that violate mutes', async () => { - const db = network.bsky.ctx.db.getPrimary() - const notif = await getLikeNotification(db, alice) - if (!notif) throw new Error('no notification found') - await pdsAgent.api.app.bsky.graph.muteActor( - { actor: notif.author }, - { headers: sc.getHeaders(alice), encoding: 'application/json' }, - ) - const views = await notifServer.getNotificationViews([notif]) - expect(views.length).toBe(0) - await pdsAgent.api.app.bsky.graph.unmuteActor( - { actor: notif.author }, - { headers: sc.getHeaders(alice), encoding: 'application/json' }, - ) - }) - - it('filters notifications that violate mutelists', async () => { - const db = network.bsky.ctx.db.getPrimary() - const notif = await getLikeNotification(db, alice) - if (!notif) throw new Error('no notification found') - const listRef = await pdsAgent.api.app.bsky.graph.list.create( - { repo: alice }, - { - name: 'mute', - purpose: 'app.bsky.graph.defs#modlist', - createdAt: new Date().toISOString(), - }, - sc.getHeaders(alice), - ) - await pdsAgent.api.app.bsky.graph.listitem.create( - { repo: alice }, - { - subject: notif.author, - list: listRef.uri, - createdAt: new Date().toISOString(), - }, - sc.getHeaders(alice), - ) - await network.processAll() - await pdsAgent.api.app.bsky.graph.muteActorList( - { list: listRef.uri }, - { headers: sc.getHeaders(alice), encoding: 'application/json' }, - ) - const views = await notifServer.getNotificationViews([notif]) - expect(views.length).toBe(0) - await pdsAgent.api.app.bsky.graph.unmuteActorList( - { list: listRef.uri }, - { headers: sc.getHeaders(alice), encoding: 'application/json' }, - ) - }) - }) - - describe('GorushNotificationServer', () => { - it('gets user tokens from db', async () => { - const tokens = await notifServer.getTokensByDid([alice]) - expect(tokens[alice][0].token).toEqual('123') - }) - - it('prepares notification to be sent', async () => { - const db = network.bsky.ctx.db.getPrimary() - const notif = await getLikeNotification(db, alice) - if (!notif) throw new Error('no notification found') - const notifAsArray = [ - notif, - notif /* second one will get dropped by rate limit */, - ] - const prepared = await notifServer.prepareNotifications(notifAsArray) - expect(prepared).toEqual([ - { - collapse_id: 'like', - collapse_key: 'like', - data: { - reason: notif.reason, - recordCid: notif.recordCid, - recordUri: notif.recordUri, - }, - message: 'again', - platform: 1, - title: 'bobby liked your post', - tokens: ['123'], - topic: 'xyz.blueskyweb.app', - }, - ]) - }) - }) - - describe('CourierNotificationServer', () => { - it('prepares notification to be sent', async () => { - const db = network.bsky.ctx.db.getPrimary() - const notif = await getLikeNotification(db, alice) - if (!notif) throw new Error('no notification found') - const courierNotifServer = new CourierNotificationServer( - db, - createCourierClient({ baseUrl: 'http://mock', httpVersion: '2' }), - ) - const prepared = await courierNotifServer.prepareNotifications([notif]) - expect(prepared[0]?.id).toBeTruthy() - expect(prepared.map((p) => p.toJson())).toEqual([ - { - id: prepared[0].id, // already ensured it exists - recipientDid: notif.did, - title: 'bobby liked your post', - message: 'again', - collapseKey: 'like', - timestamp: notif.sortAt, - // this is missing, appears to be a quirk of toJson() - // alwaysDeliver: false, - additional: { - reason: notif.reason, - uri: notif.recordUri, - subject: notif.reasonSubject, - }, - }, - ]) - }) - }) - - async function getLikeNotification(db: Database, did: string) { - return await db.db - .selectFrom('notification') - .selectAll() - .where('did', '=', did) - .where('reason', '=', 'like') - .orderBy('sortAt') - .executeTakeFirst() - } -}) diff --git a/packages/bsky/tests/pipeline/backpressure.test.ts b/packages/bsky/tests/pipeline/backpressure.test.ts deleted file mode 100644 index 87e01b8cc89..00000000000 --- a/packages/bsky/tests/pipeline/backpressure.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { wait } from '@atproto/common' -import { - BskyIndexers, - TestNetworkNoAppView, - getIndexers, - getIngester, - processAll, - SeedClient, - basicSeed, -} from '@atproto/dev-env' -import { BskyIngester } from '../../src' - -const TEST_NAME = 'pipeline_backpressure' - -describe('pipeline backpressure', () => { - let network: TestNetworkNoAppView - let ingester: BskyIngester - let indexers: BskyIndexers - - let sc: SeedClient - - beforeAll(async () => { - network = await TestNetworkNoAppView.create({ - dbPostgresSchema: TEST_NAME, - }) - ingester = await getIngester(network, { - name: TEST_NAME, - ingesterPartitionCount: 2, - ingesterMaxItems: 10, - ingesterCheckItemsEveryN: 5, - }) - indexers = await getIndexers(network, { - name: TEST_NAME, - partitionIdsByIndexer: [[0], [1]], - }) - sc = network.getSeedClient() - await basicSeed(sc) - }) - - afterAll(async () => { - await network.close() - }) - - it('ingester issues backpressure based on total of partition lengths.', async () => { - // ingest until first 10 are seen - await ingester.start() - while ((ingester.sub.lastSeq ?? 0) < 10) { - await wait(50) - } - // allow additional time to pass to ensure no additional events are being consumed - await wait(200) - // check that max items has been respected (i.e. backpressure was applied) - const lengths = await ingester.ctx.redis.streamLengths(['repo:0', 'repo:1']) - expect(lengths).toHaveLength(2) - expect(lengths[0] + lengths[1]).toBeLessThanOrEqual(10 + 5) // not exact due to batching, may catch on following check backpressure - // drain all items using indexers, releasing backpressure - await indexers.start() - await processAll(network, ingester) - const lengthsFinal = await ingester.ctx.redis.streamLengths([ - 'repo:0', - 'repo:1', - ]) - expect(lengthsFinal).toHaveLength(2) - expect(lengthsFinal[0] + lengthsFinal[1]).toEqual(0) - await indexers.destroy() - await ingester.destroy() - }) -}) diff --git a/packages/bsky/tests/pipeline/reingest.test.ts b/packages/bsky/tests/pipeline/reingest.test.ts deleted file mode 100644 index 8d90f9fea8f..00000000000 --- a/packages/bsky/tests/pipeline/reingest.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { - TestNetworkNoAppView, - SeedClient, - getIngester, - ingestAll, - basicSeed, -} from '@atproto/dev-env' -import { BskyIngester } from '../../src' - -const TEST_NAME = 'pipeline_reingest' - -describe('pipeline reingestion', () => { - let network: TestNetworkNoAppView - let ingester: BskyIngester - let sc: SeedClient - - beforeAll(async () => { - network = await TestNetworkNoAppView.create({ - dbPostgresSchema: TEST_NAME, - }) - ingester = await getIngester(network, { - name: TEST_NAME, - ingesterPartitionCount: 1, - }) - sc = network.getSeedClient() - await basicSeed(sc) - }) - - afterAll(async () => { - await network.close() - await ingester.destroy() - }) - - it('allows events to be reingested multiple times.', async () => { - // ingest all events once - await ingester.start() - await ingestAll(network, ingester) - const initialCursor = await ingester.sub.getCursor() - const [initialLen] = await ingester.ctx.redis.streamLengths(['repo:0']) - expect(initialCursor).toBeGreaterThan(10) - expect(initialLen).toBeGreaterThan(10) - // stop ingesting and reset ingester state - await ingester.sub.destroy() - await ingester.sub.resetCursor() - // add one new event and reingest - await sc.post(sc.dids.alice, 'one more event!') // add one event to firehose - ingester.sub.resume() - await ingestAll(network, ingester) - // confirm the newest event was ingested - const finalCursor = await ingester.sub.getCursor() - const [finalLen] = await ingester.ctx.redis.streamLengths(['repo:0']) - expect(finalCursor).toEqual(initialCursor + 1) - expect(finalLen).toEqual(initialLen + 1) - }) -}) diff --git a/packages/bsky/tests/pipeline/repartition.test.ts b/packages/bsky/tests/pipeline/repartition.test.ts deleted file mode 100644 index 2c7470fc06d..00000000000 --- a/packages/bsky/tests/pipeline/repartition.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { - BskyIndexers, - TestNetworkNoAppView, - SeedClient, - getIndexers, - getIngester, - ingestAll, - processAll, - usersSeed, -} from '@atproto/dev-env' -import { BskyIngester } from '../../src' -import { countAll } from '../../src/db/util' - -const TEST_NAME = 'pipeline_repartition' - -describe('pipeline indexer repartitioning', () => { - let network: TestNetworkNoAppView - let ingester: BskyIngester - let indexers1: BskyIndexers - let indexers2: BskyIndexers - let sc: SeedClient - - beforeAll(async () => { - network = await TestNetworkNoAppView.create({ - dbPostgresSchema: TEST_NAME, - }) - ingester = await getIngester(network, { - name: TEST_NAME, - ingesterPartitionCount: 2, - }) - indexers1 = await getIndexers(network, { - name: TEST_NAME, - partitionIdsByIndexer: [[0, 1]], // one indexer consuming two partitions - }) - indexers2 = await getIndexers(network, { - name: TEST_NAME, - partitionIdsByIndexer: [[0], [1]], // two indexers, each consuming one partition - }) - sc = network.getSeedClient() - await usersSeed(sc) - }) - - afterAll(async () => { - await network.close() - }) - - it('indexers repartition without missing events.', async () => { - const poster = createPoster(sc) - await Promise.all([poster.post(4), indexers1.start(), ingester.start()]) - await poster.post(1) - await processAll(network, ingester) - const { count: indexedPosts } = await indexers1.db.db - .selectFrom('post') - .select(countAll.as('count')) - .executeTakeFirstOrThrow() - expect(indexedPosts).toEqual(5) - await Promise.all([poster.post(3), indexers1.destroy()]) - await poster.post(3) // miss some events - await ingestAll(network, ingester) - await Promise.all([poster.post(3), indexers2.start()]) // handle some events on indexers2 - await processAll(network, ingester) - const { count: allIndexedPosts } = await indexers2.db.db - .selectFrom('post') - .select(countAll.as('count')) - .executeTakeFirstOrThrow() - expect(allIndexedPosts).toBeGreaterThan(indexedPosts) - expect(allIndexedPosts).toEqual(poster.postCount) - await indexers2.destroy() - await ingester.destroy() - }) -}) - -function createPoster(sc: SeedClient) { - return { - postCount: 0, - destroyed: false, - async post(n = 1) { - const dids = Object.values(sc.dids) - for (let i = 0; i < n; ++i) { - const did = dids[this.postCount % dids.length] - await sc.post(did, `post ${this.postCount}`) - this.postCount++ - } - }, - } -} diff --git a/packages/bsky/tests/reprocessing.test.ts b/packages/bsky/tests/reprocessing.test.ts deleted file mode 100644 index fd9199379c7..00000000000 --- a/packages/bsky/tests/reprocessing.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import axios from 'axios' -import { AtUri } from '@atproto/syntax' -import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' -import { Database } from '../src/db' - -describe('reprocessing', () => { - let network: TestNetwork - let sc: SeedClient - let alice: string - - beforeAll(async () => { - network = await TestNetwork.create({ - dbPostgresSchema: 'bsky_reprocessing', - }) - sc = network.getSeedClient() - await basicSeed(sc) - alice = sc.dids.alice - await network.processAll() - }) - - afterAll(async () => { - await network.close() - }) - - const getRecordUris = async (db: Database, did: string) => { - const res = await db.db - .selectFrom('record') - .select('uri') - .where('did', '=', did) - .execute() - return res.map((row) => row.uri) - } - it('reprocesses repo data', async () => { - const db = network.bsky.ctx.db.getPrimary() - const urisBefore = await getRecordUris(db, alice) - await db.db.deleteFrom('record').where('did', '=', alice).execute() - const indexerPort = network.bsky.indexer.ctx.cfg.indexerPort - await axios.post(`http://localhost:${indexerPort}/reprocess/${alice}`) - await network.processAll() - const urisAfter = await getRecordUris(db, alice) - expect(urisAfter.sort()).toEqual(urisBefore.sort()) - }) - - it('buffers commits while reprocessing repo data', async () => { - const db = network.bsky.ctx.db.getPrimary() - const urisBefore = await getRecordUris(db, alice) - await db.db.deleteFrom('record').where('did', '=', alice).execute() - const indexerPort = network.bsky.indexer.ctx.cfg.indexerPort - const toDeleteIndex = urisBefore.findIndex((uri) => - uri.includes('app.bsky.feed.post'), - ) - if (toDeleteIndex < 0) { - throw new Error('could not find post to delete') - } - // request reprocess while buffering a new post & delete - const [newPost] = await Promise.all([ - sc.post(alice, 'blah blah'), - axios.post(`http://localhost:${indexerPort}/reprocess/${alice}`), - sc.deletePost(alice, new AtUri(urisBefore[toDeleteIndex])), - ]) - await network.processAll() - const urisAfter = await getRecordUris(db, alice) - const expected = [ - ...urisBefore.slice(0, toDeleteIndex), - ...urisBefore.slice(toDeleteIndex + 1), - newPost.ref.uriStr, - ] - expect(urisAfter.sort()).toEqual(expected.sort()) - }) -}) diff --git a/packages/bsky/tests/server.test.ts b/packages/bsky/tests/server.test.ts index 157b352136f..3cc0257a4eb 100644 --- a/packages/bsky/tests/server.test.ts +++ b/packages/bsky/tests/server.test.ts @@ -3,11 +3,10 @@ import express from 'express' import axios, { AxiosError } from 'axios' import { TestNetwork, basicSeed } from '@atproto/dev-env' import { handler as errorHandler } from '../src/error' -import { Database } from '../src' +import { once } from 'events' describe('server', () => { let network: TestNetwork - let db: Database let alice: string beforeAll(async () => { @@ -18,7 +17,6 @@ describe('server', () => { await basicSeed(sc) await network.processAll() alice = sc.dids.alice - db = network.bsky.ctx.db.getPrimary() }) afterAll(async () => { @@ -56,7 +54,7 @@ describe('server', () => { it('healthcheck succeeds when database is available.', async () => { const { data, status } = await axios.get(`${network.bsky.url}/xrpc/_health`) expect(status).toEqual(200) - expect(data).toEqual({ version: '0.0.0' }) + expect(data).toEqual({ version: 'unknown' }) }) // TODO(bsky) check on a different endpoint that accepts json, currently none. @@ -107,10 +105,9 @@ describe('server', () => { expect(res.headers['content-encoding']).toBeUndefined() }) - it('healthcheck fails when database is unavailable.', async () => { - await network.bsky.ingester.sub.destroy() - await network.bsky.indexer.sub.destroy() - await db.close() + it('healthcheck fails when dataplane is unavailable.', async () => { + const { port } = network.bsky.dataplane.server.address() as AddressInfo + await network.bsky.dataplane.destroy() let error: AxiosError try { await axios.get(`${network.bsky.url}/xrpc/_health`) @@ -121,10 +118,14 @@ describe('server', () => { } else { throw err } + } finally { + // restart dataplane server to allow test suite to cleanup + network.bsky.dataplane.server.listen(port) + await once(network.bsky.dataplane.server, 'listening') } expect(error.response?.status).toEqual(503) expect(error.response?.data).toEqual({ - version: '0.0.0', + version: 'unknown', error: 'Service Unavailable', }) }) diff --git a/packages/bsky/tests/subscription/mutes.test.ts b/packages/bsky/tests/subscription/mutes.test.ts deleted file mode 100644 index 9b3f194050b..00000000000 --- a/packages/bsky/tests/subscription/mutes.test.ts +++ /dev/null @@ -1,170 +0,0 @@ -import AtpAgent from '@atproto/api' -import { wait } from '@atproto/common' -import { TestNetwork, SeedClient, basicSeed, TestBsync } from '@atproto/dev-env' -import assert from 'assert' - -describe('sync mutes', () => { - let network: TestNetwork - let bsync: TestBsync - let pdsAgent: AtpAgent - let sc: SeedClient - - beforeAll(async () => { - assert(process.env.DB_POSTGRES_URL) - bsync = await TestBsync.create({ - dbSchema: 'bsync_subscription_mutes', - dbUrl: process.env.DB_POSTGRES_URL, - }) - network = await TestNetwork.create({ - dbPostgresSchema: 'bsky_subscription_mutes', - bsky: { - bsyncUrl: bsync.url, - bsyncApiKey: [...bsync.ctx.cfg.auth.apiKeys][0], - bsyncHttpVersion: '1.1', - bsyncOnlyMutes: true, - ingester: { - bsyncUrl: bsync.url, - bsyncApiKey: [...bsync.ctx.cfg.auth.apiKeys][0], - bsyncHttpVersion: '1.1', - }, - }, - }) - pdsAgent = network.pds.getClient() - sc = network.getSeedClient() - await basicSeed(sc) - }) - - afterAll(async () => { - await network.close() - await bsync.close() - }) - - it('mutes and unmutes actors.', async () => { - await pdsAgent.api.app.bsky.graph.muteActor( - { actor: sc.dids.alice }, - { headers: sc.getHeaders(sc.dids.bob), encoding: 'application/json' }, - ) - await pdsAgent.api.app.bsky.graph.muteActor( - { actor: sc.dids.carol }, - { headers: sc.getHeaders(sc.dids.bob), encoding: 'application/json' }, - ) - await pdsAgent.api.app.bsky.graph.muteActor( - { actor: sc.dids.dan }, - { headers: sc.getHeaders(sc.dids.bob), encoding: 'application/json' }, - ) - await processAllMuteOps(network, bsync) - const { data: mutes1 } = await pdsAgent.api.app.bsky.graph.getMutes( - {}, - { headers: sc.getHeaders(sc.dids.bob) }, - ) - expect(mutes1.mutes.map((mute) => mute.did)).toEqual([ - sc.dids.dan, - sc.dids.carol, - sc.dids.alice, - ]) - await pdsAgent.api.app.bsky.graph.unmuteActor( - { actor: sc.dids.carol }, - { headers: sc.getHeaders(sc.dids.bob), encoding: 'application/json' }, - ) - await processAllMuteOps(network, bsync) - const { data: mutes2 } = await pdsAgent.api.app.bsky.graph.getMutes( - {}, - { headers: sc.getHeaders(sc.dids.bob) }, - ) - expect(mutes2.mutes.map((mute) => mute.did)).toEqual([ - sc.dids.dan, - sc.dids.alice, - ]) - }) - - it('mutes and unmutes lists.', async () => { - // create lists - const list1 = await pdsAgent.api.app.bsky.graph.list.create( - { repo: sc.dids.bob }, - { - name: 'mod list 1', - purpose: 'app.bsky.graph.defs#modlist', - createdAt: new Date().toISOString(), - }, - sc.getHeaders(sc.dids.bob), - ) - const list2 = await pdsAgent.api.app.bsky.graph.list.create( - { repo: sc.dids.bob }, - { - name: 'mod list 2', - purpose: 'app.bsky.graph.defs#modlist', - createdAt: new Date().toISOString(), - }, - sc.getHeaders(sc.dids.bob), - ) - const list3 = await pdsAgent.api.app.bsky.graph.list.create( - { repo: sc.dids.bob }, - { - name: 'mod list 3', - purpose: 'app.bsky.graph.defs#modlist', - createdAt: new Date().toISOString(), - }, - sc.getHeaders(sc.dids.bob), - ) - await network.processAll() - await pdsAgent.api.app.bsky.graph.muteActorList( - { list: list1.uri }, - { headers: sc.getHeaders(sc.dids.bob), encoding: 'application/json' }, - ) - await pdsAgent.api.app.bsky.graph.muteActorList( - { list: list2.uri }, - { headers: sc.getHeaders(sc.dids.bob), encoding: 'application/json' }, - ) - await pdsAgent.api.app.bsky.graph.muteActorList( - { list: list3.uri }, - { headers: sc.getHeaders(sc.dids.bob), encoding: 'application/json' }, - ) - await processAllMuteOps(network, bsync) - const { data: listmutes1 } = await pdsAgent.api.app.bsky.graph.getListMutes( - {}, - { headers: sc.getHeaders(sc.dids.bob) }, - ) - expect(listmutes1.lists.map((list) => list.uri)).toEqual([ - list3.uri, - list2.uri, - list1.uri, - ]) - await pdsAgent.api.app.bsky.graph.unmuteActorList( - { list: list2.uri }, - { headers: sc.getHeaders(sc.dids.bob), encoding: 'application/json' }, - ) - await processAllMuteOps(network, bsync) - const { data: listmutes2 } = await pdsAgent.api.app.bsky.graph.getListMutes( - {}, - { headers: sc.getHeaders(sc.dids.bob) }, - ) - expect(listmutes2.lists.map((list) => list.uri)).toEqual([ - list3.uri, - list1.uri, - ]) - }) -}) - -async function processAllMuteOps(network: TestNetwork, bsync: TestBsync) { - const getBsyncCursor = async () => { - const result = await bsync.ctx.db.db - .selectFrom('mute_op') - .orderBy('id', 'desc') - .select('id') - .limit(1) - .executeTakeFirst() - return result?.id.toString() ?? null - } - assert(network.bsky.ingester.ctx.muteSubscription) - let total = 0 - while ( - (await getBsyncCursor()) !== - network.bsky.ingester.ctx.muteSubscription.cursor - ) { - if (total > 5000) { - throw new Error('timeout while processing mute ops') - } - await wait(50) - total += 50 - } -} diff --git a/packages/bsky/tests/views/__snapshots__/author-feed.test.ts.snap b/packages/bsky/tests/views/__snapshots__/author-feed.test.ts.snap index 37478713bd9..6112f126d97 100644 --- a/packages/bsky/tests/views/__snapshots__/author-feed.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/author-feed.test.ts.snap @@ -89,7 +89,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(3)", "val": "test-label", }, @@ -97,7 +97,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(3)", "val": "test-label-2", }, @@ -221,7 +221,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(5)", + "did": "user(4)", "handle": "dan.test", "labels": Array [], "viewer": Object { @@ -237,7 +237,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(6)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -332,7 +332,7 @@ Array [ "cid": "cids(6)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(6)", "val": "test-label", }, @@ -498,7 +498,7 @@ Array [ "cid": "cids(0)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(2)", + "src": "did:example:labeler", "uri": "record(0)", "val": "test-label", }, @@ -506,7 +506,7 @@ Array [ "cid": "cids(0)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(2)", + "src": "did:example:labeler", "uri": "record(0)", "val": "test-label-2", }, @@ -552,8 +552,8 @@ Array [ "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", - "did": "user(3)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", + "did": "user(2)", "displayName": "ali", "handle": "alice.test", "labels": Array [ @@ -561,7 +561,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "user(2)", "uri": "record(4)", "val": "self-label-a", }, @@ -569,7 +569,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "user(2)", "uri": "record(4)", "val": "self-label-b", }, @@ -600,8 +600,8 @@ Array [ "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", - "did": "user(3)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", + "did": "user(2)", "displayName": "ali", "handle": "alice.test", "labels": Array [ @@ -609,7 +609,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "user(2)", "uri": "record(4)", "val": "self-label-a", }, @@ -617,7 +617,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "user(2)", "uri": "record(4)", "val": "self-label-b", }, @@ -746,13 +746,13 @@ Array [ "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(2)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(2)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(2)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(2)@jpeg", }, Object { "alt": "../dev-env/src/seed/img/key-alt.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(3)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(3)@jpeg", }, ], }, @@ -760,8 +760,8 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(5)@jpeg", - "did": "user(3)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(5)@jpeg", + "did": "user(4)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], @@ -1037,13 +1037,13 @@ Array [ "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(2)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(2)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(2)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(2)@jpeg", }, Object { "alt": "../dev-env/src/seed/img/key-alt.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(3)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(3)@jpeg", }, ], }, @@ -1051,8 +1051,8 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(5)@jpeg", - "did": "user(3)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(5)@jpeg", + "did": "user(4)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], @@ -1236,7 +1236,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(4)", "val": "test-label", }, @@ -1244,7 +1244,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(4)", "val": "test-label-2", }, @@ -1415,7 +1415,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(6)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -1432,13 +1432,13 @@ Array [ "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(7)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(7)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(5)@jpeg", }, Object { "alt": "../dev-env/src/seed/img/key-alt.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(7)/cids(8)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(7)/cids(8)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(8)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(8)@jpeg", }, ], }, @@ -1674,7 +1674,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(5)", "val": "test-label", }, @@ -1682,7 +1682,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(5)", "val": "test-label-2", }, @@ -1812,7 +1812,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(5)", + "did": "user(4)", "handle": "dan.test", "labels": Array [], "viewer": Object { @@ -1827,7 +1827,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(6)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -1920,7 +1920,7 @@ Array [ "cid": "cids(6)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(8)", "val": "test-label", }, diff --git a/packages/bsky/tests/views/__snapshots__/block-lists.test.ts.snap b/packages/bsky/tests/views/__snapshots__/block-lists.test.ts.snap index a3cfb905dc9..c5ff586f5c2 100644 --- a/packages/bsky/tests/views/__snapshots__/block-lists.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/block-lists.test.ts.snap @@ -103,7 +103,7 @@ Object { "cid": "cids(0)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(0)", "val": "test-label", }, @@ -288,7 +288,7 @@ Object { exports[`pds views with blocking from block lists returns a users own list blocks 1`] = ` Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "lists": Array [ Object { "cid": "cids(0)", @@ -383,7 +383,7 @@ Object { exports[`pds views with blocking from block lists returns lists associated with a user 1`] = ` Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "lists": Array [ Object { "cid": "cids(0)", @@ -477,7 +477,7 @@ Object { exports[`pds views with blocking from block lists returns the contents of a list 1`] = ` Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "items": Array [ Object { "subject": Object { diff --git a/packages/bsky/tests/views/__snapshots__/blocks.test.ts.snap b/packages/bsky/tests/views/__snapshots__/blocks.test.ts.snap index d5ecf9b2c7e..751dc15b3ac 100644 --- a/packages/bsky/tests/views/__snapshots__/blocks.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/blocks.test.ts.snap @@ -103,7 +103,7 @@ Object { "cid": "cids(0)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(0)", "val": "test-label", }, @@ -301,7 +301,7 @@ Object { "cid": "cids(3)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(7)", "val": "test-label", }, @@ -309,7 +309,7 @@ Object { "cid": "cids(3)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(7)", "val": "test-label-2", }, diff --git a/packages/bsky/tests/views/__snapshots__/follows.test.ts.snap b/packages/bsky/tests/views/__snapshots__/follows.test.ts.snap index dfb5ff2ecb3..fcd5003475b 100644 --- a/packages/bsky/tests/views/__snapshots__/follows.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/follows.test.ts.snap @@ -2,7 +2,7 @@ exports[`pds follow views fetches followers 1`] = ` Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "followers": Array [ Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", @@ -83,7 +83,7 @@ Object { exports[`pds follow views fetches followers 2`] = ` Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "followers": Array [ Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", @@ -134,7 +134,7 @@ Object { exports[`pds follow views fetches followers 3`] = ` Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "followers": Array [ Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", @@ -200,7 +200,7 @@ Object { exports[`pds follow views fetches followers 4`] = ` Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "followers": Array [ Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", @@ -236,7 +236,7 @@ Object { exports[`pds follow views fetches followers 5`] = ` Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "followers": Array [ Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", @@ -287,7 +287,7 @@ Object { exports[`pds follow views fetches follows 1`] = ` Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "follows": Array [ Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", @@ -368,7 +368,7 @@ Object { exports[`pds follow views fetches follows 2`] = ` Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "follows": Array [ Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", @@ -419,7 +419,7 @@ Object { exports[`pds follow views fetches follows 3`] = ` Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "follows": Array [ Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", @@ -455,7 +455,7 @@ Object { exports[`pds follow views fetches follows 4`] = ` Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "follows": Array [ Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", @@ -521,7 +521,7 @@ Object { exports[`pds follow views fetches follows 5`] = ` Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "follows": Array [ Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(0)@jpeg", @@ -589,11 +589,6 @@ Object { "did": "user(2)", "following": "record(2)", }, - Object { - "$type": "app.bsky.graph.defs#notFoundActor", - "actor": "did:example:fake", - "notFound": true, - }, ], } `; diff --git a/packages/bsky/tests/views/__snapshots__/likes.test.ts.snap b/packages/bsky/tests/views/__snapshots__/likes.test.ts.snap index 426467a3fa7..c9ad1536f85 100644 --- a/packages/bsky/tests/views/__snapshots__/likes.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/likes.test.ts.snap @@ -2,7 +2,7 @@ exports[`pds like views fetches post likes 1`] = ` Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "likes": Array [ Object { "actor": Object { @@ -72,7 +72,7 @@ Object { exports[`pds like views fetches reply likes 1`] = ` Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "likes": Array [ Object { "actor": Object { diff --git a/packages/bsky/tests/views/__snapshots__/list-feed.test.ts.snap b/packages/bsky/tests/views/__snapshots__/list-feed.test.ts.snap index 790cc5db4e6..f7887147f39 100644 --- a/packages/bsky/tests/views/__snapshots__/list-feed.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/list-feed.test.ts.snap @@ -90,7 +90,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(5)", "val": "test-label", }, @@ -98,7 +98,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(5)", "val": "test-label-2", }, @@ -221,7 +221,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(5)", "val": "test-label", }, @@ -229,7 +229,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(5)", "val": "test-label-2", }, @@ -408,7 +408,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(5)", + "did": "user(4)", "handle": "dan.test", "labels": Array [], "viewer": Object { @@ -423,7 +423,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(6)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -516,7 +516,7 @@ Array [ "cid": "cids(6)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(8)", "val": "test-label", }, diff --git a/packages/bsky/tests/views/__snapshots__/mute-lists.test.ts.snap b/packages/bsky/tests/views/__snapshots__/mute-lists.test.ts.snap index 8b46475eafe..4cd94ec2efc 100644 --- a/packages/bsky/tests/views/__snapshots__/mute-lists.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/mute-lists.test.ts.snap @@ -38,9 +38,11 @@ Object { "cid": "cids(4)", "creator": Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", + "description": "its me!", "did": "user(0)", "displayName": "ali", "handle": "alice.test", + "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { "cid": "cids(2)", @@ -240,7 +242,7 @@ Object { "cid": "cids(5)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(9)", "val": "test-label", }, @@ -248,7 +250,7 @@ Object { "cid": "cids(5)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(9)", "val": "test-label-2", }, @@ -297,7 +299,7 @@ Object { exports[`bsky views with mutes from mute lists returns a users own list mutes 1`] = ` Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "lists": Array [ Object { "cid": "cids(0)", @@ -390,7 +392,7 @@ Object { exports[`bsky views with mutes from mute lists returns lists associated with a user 1`] = ` Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "lists": Array [ Object { "cid": "cids(0)", @@ -483,7 +485,7 @@ Object { exports[`bsky views with mutes from mute lists returns the contents of a list 1`] = ` Object { - "cursor": "0000000000000::bafycid", + "cursor": "0000000000000__bafycid", "items": Array [ Object { "subject": Object { diff --git a/packages/bsky/tests/views/__snapshots__/mutes.test.ts.snap b/packages/bsky/tests/views/__snapshots__/mutes.test.ts.snap index 655d7b62cb6..90919028294 100644 --- a/packages/bsky/tests/views/__snapshots__/mutes.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/mutes.test.ts.snap @@ -217,7 +217,7 @@ Object { "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(5)", "val": "test-label", }, @@ -225,7 +225,7 @@ Object { "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(5)", "val": "test-label-2", }, diff --git a/packages/bsky/tests/views/__snapshots__/notifications.test.ts.snap b/packages/bsky/tests/views/__snapshots__/notifications.test.ts.snap index e2ac2d587c0..9a3d1d088b8 100644 --- a/packages/bsky/tests/views/__snapshots__/notifications.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/notifications.test.ts.snap @@ -250,7 +250,7 @@ Array [ "cid": "cids(12)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(14)", "val": "test-label", }, @@ -258,7 +258,7 @@ Array [ "cid": "cids(12)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(14)", "val": "test-label-2", }, @@ -380,7 +380,7 @@ Array [ }, "cid": "cids(0)", "indexedAt": "1970-01-01T00:00:00.000Z", - "isRead": false, + "isRead": true, "labels": Array [], "reason": "repost", "reasonSubject": "record(2)", @@ -407,7 +407,7 @@ Array [ }, "cid": "cids(2)", "indexedAt": "1970-01-01T00:00:00.000Z", - "isRead": false, + "isRead": true, "labels": Array [], "reason": "repost", "reasonSubject": "record(4)", @@ -434,7 +434,7 @@ Array [ }, "cid": "cids(4)", "indexedAt": "1970-01-01T00:00:00.000Z", - "isRead": false, + "isRead": true, "labels": Array [], "reason": "mention", "record": Object { @@ -478,7 +478,7 @@ Array [ }, "cid": "cids(6)", "indexedAt": "1970-01-01T00:00:00.000Z", - "isRead": false, + "isRead": true, "labels": Array [], "reason": "like", "reasonSubject": "record(4)", @@ -506,7 +506,7 @@ Array [ }, "cid": "cids(7)", "indexedAt": "1970-01-01T00:00:00.000Z", - "isRead": false, + "isRead": true, "labels": Array [], "reason": "follow", "record": Object { @@ -565,7 +565,7 @@ Array [ }, "cid": "cids(9)", "indexedAt": "1970-01-01T00:00:00.000Z", - "isRead": false, + "isRead": true, "labels": Array [], "reason": "reply", "reasonSubject": "record(4)", @@ -600,7 +600,7 @@ Array [ }, "cid": "cids(10)", "indexedAt": "1970-01-01T00:00:00.000Z", - "isRead": false, + "isRead": true, "labels": Array [], "reason": "like", "reasonSubject": "record(13)", @@ -628,7 +628,7 @@ Array [ }, "cid": "cids(12)", "indexedAt": "1970-01-01T00:00:00.000Z", - "isRead": false, + "isRead": true, "labels": Array [], "reason": "like", "reasonSubject": "record(4)", @@ -660,7 +660,7 @@ Array [ }, "cid": "cids(13)", "indexedAt": "1970-01-01T00:00:00.000Z", - "isRead": false, + "isRead": true, "labels": Array [], "reason": "follow", "record": Object { @@ -688,13 +688,13 @@ Array [ }, "cid": "cids(15)", "indexedAt": "1970-01-01T00:00:00.000Z", - "isRead": false, + "isRead": true, "labels": Array [ Object { "cid": "cids(15)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(17)", "val": "test-label", }, @@ -702,7 +702,7 @@ Array [ "cid": "cids(15)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(17)", "val": "test-label-2", }, @@ -760,7 +760,7 @@ Array [ }, "cid": "cids(17)", "indexedAt": "1970-01-01T00:00:00.000Z", - "isRead": false, + "isRead": true, "labels": Array [], "reason": "like", "reasonSubject": "record(13)", @@ -792,7 +792,7 @@ Array [ }, "cid": "cids(18)", "indexedAt": "1970-01-01T00:00:00.000Z", - "isRead": false, + "isRead": true, "labels": Array [], "reason": "like", "reasonSubject": "record(4)", @@ -871,7 +871,7 @@ Array [ }, "cid": "cids(2)", "indexedAt": "1970-01-01T00:00:00.000Z", - "isRead": false, + "isRead": true, "labels": Array [], "reason": "follow", "record": Object { @@ -915,13 +915,13 @@ Array [ }, "cid": "cids(5)", "indexedAt": "1970-01-01T00:00:00.000Z", - "isRead": false, + "isRead": true, "labels": Array [ Object { "cid": "cids(5)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(4)", "val": "test-label", }, diff --git a/packages/bsky/tests/views/__snapshots__/thread.test.ts.snap b/packages/bsky/tests/views/__snapshots__/thread.test.ts.snap index dd71dc9010d..41a448f63eb 100644 --- a/packages/bsky/tests/views/__snapshots__/thread.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/thread.test.ts.snap @@ -54,7 +54,6 @@ Object { "like": "record(7)", }, }, - "replies": Array [], }, "post": Object { "author": Object { @@ -85,7 +84,7 @@ Object { "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(5)", "val": "test-label", }, @@ -93,7 +92,7 @@ Object { "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(5)", "val": "test-label-2", }, @@ -135,7 +134,6 @@ Object { "uri": "record(5)", "viewer": Object {}, }, - "replies": Array [], }, "post": Object { "author": Object { @@ -319,7 +317,7 @@ Object { "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(7)", "val": "test-label", }, @@ -327,7 +325,7 @@ Object { "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(7)", "val": "test-label-2", }, @@ -557,7 +555,7 @@ Object { "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(7)", "val": "test-label", }, @@ -565,7 +563,7 @@ Object { "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(7)", "val": "test-label-2", }, @@ -1073,6 +1071,11 @@ Object { }, }, "replies": Array [ + Object { + "$type": "app.bsky.feed.defs#notFoundPost", + "notFound": true, + "uri": "record(5)", + }, Object { "$type": "app.bsky.feed.defs#threadViewPost", "post": Object { @@ -1104,16 +1107,16 @@ Object { "cid": "cids(3)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", - "uri": "record(5)", + "src": "did:example:labeler", + "uri": "record(6)", "val": "test-label", }, Object { "cid": "cids(3)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", - "uri": "record(5)", + "src": "did:example:labeler", + "uri": "record(6)", "val": "test-label-2", }, ], @@ -1151,7 +1154,7 @@ Object { }, "replyCount": 1, "repostCount": 0, - "uri": "record(5)", + "uri": "record(6)", "viewer": Object {}, }, "replies": Array [ @@ -1198,7 +1201,7 @@ Object { "reply": Object { "parent": Object { "cid": "cids(3)", - "uri": "record(5)", + "uri": "record(6)", }, "root": Object { "cid": "cids(0)", @@ -1209,9 +1212,9 @@ Object { }, "replyCount": 0, "repostCount": 2, - "uri": "record(6)", + "uri": "record(7)", "viewer": Object { - "repost": "record(7)", + "repost": "record(8)", }, }, "replies": Array [], @@ -1304,7 +1307,7 @@ Object { "cid": "cids(3)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(5)", "val": "test-label", }, @@ -1312,7 +1315,7 @@ Object { "cid": "cids(3)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(5)", "val": "test-label-2", }, diff --git a/packages/bsky/tests/views/__snapshots__/timeline.test.ts.snap b/packages/bsky/tests/views/__snapshots__/timeline.test.ts.snap index 0817313a331..32c41656733 100644 --- a/packages/bsky/tests/views/__snapshots__/timeline.test.ts.snap +++ b/packages/bsky/tests/views/__snapshots__/timeline.test.ts.snap @@ -278,7 +278,7 @@ Array [ "cid": "cids(5)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(5)", "val": "test-label", }, @@ -853,7 +853,7 @@ Array [ "cid": "cids(6)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(8)", "val": "test-label", }, @@ -880,8 +880,8 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(1)@jpeg", - "did": "user(5)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(1)@jpeg", + "did": "user(4)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], @@ -1000,13 +1000,13 @@ Array [ "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(7)/cids(11)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(7)/cids(11)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(11)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(11)@jpeg", }, Object { "alt": "../dev-env/src/seed/img/key-alt.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(7)/cids(12)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(7)/cids(12)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(12)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(12)@jpeg", }, ], }, @@ -1014,8 +1014,8 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(1)@jpeg", - "did": "user(5)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(1)@jpeg", + "did": "user(4)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], @@ -1034,9 +1034,9 @@ Array [ "cid": "cids(13)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(15)", - "val": "kind", + "val": "test-label-3", }, ], "uri": "record(15)", @@ -1058,9 +1058,9 @@ Array [ "cid": "cids(10)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(14)", - "val": "kind", + "val": "test-label-3", }, ], "likeCount": 2, @@ -1116,8 +1116,8 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(1)@jpeg", - "did": "user(5)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(1)@jpeg", + "did": "user(4)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], @@ -1135,9 +1135,9 @@ Array [ "cid": "cids(13)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", + "src": "did:example:labeler", "uri": "record(15)", - "val": "kind", + "val": "test-label-3", }, ], "likeCount": 0, @@ -1324,7 +1324,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(3)", "val": "test-label", }, @@ -1332,7 +1332,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(3)", "val": "test-label-2", }, @@ -1497,7 +1497,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(6)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -1516,13 +1516,13 @@ Array [ "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(7)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(7)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(5)@jpeg", }, Object { "alt": "../dev-env/src/seed/img/key-alt.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(7)/cids(8)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(7)/cids(8)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(8)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(8)@jpeg", }, ], }, @@ -1549,9 +1549,9 @@ Array [ "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(11)", - "val": "kind", + "val": "test-label-3", }, ], "uri": "record(11)", @@ -1574,9 +1574,9 @@ Array [ "cid": "cids(7)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(8)", - "val": "kind", + "val": "test-label-3", }, ], "uri": "record(8)", @@ -1660,7 +1660,7 @@ Array [ "reason": Object { "$type": "app.bsky.feed.defs#reasonRepost", "by": Object { - "did": "user(6)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -1760,7 +1760,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(3)", "val": "test-label", }, @@ -1768,7 +1768,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(3)", "val": "test-label-2", }, @@ -1859,7 +1859,7 @@ Array [ Object { "post": Object { "author": Object { - "did": "user(6)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -2016,7 +2016,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(3)", "val": "test-label", }, @@ -2024,7 +2024,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(3)", "val": "test-label-2", }, @@ -2209,7 +2209,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(6)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -2226,9 +2226,9 @@ Array [ "cid": "cids(7)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(8)", - "val": "kind", + "val": "test-label-3", }, ], "uri": "record(8)", @@ -2313,7 +2313,7 @@ Array [ "cid": "cids(11)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(13)", "val": "test-label", }, @@ -2430,7 +2430,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(6)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -2449,13 +2449,13 @@ Array [ "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(7)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(7)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(5)@jpeg", }, Object { "alt": "../dev-env/src/seed/img/key-alt.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(7)/cids(8)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(7)/cids(8)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(8)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(8)@jpeg", }, ], }, @@ -2482,9 +2482,9 @@ Array [ "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(11)", - "val": "kind", + "val": "test-label-3", }, ], "uri": "record(11)", @@ -2507,9 +2507,9 @@ Array [ "cid": "cids(7)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(8)", - "val": "kind", + "val": "test-label-3", }, ], "uri": "record(8)", @@ -2621,7 +2621,7 @@ Array [ Object { "post": Object { "author": Object { - "did": "user(6)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -2639,13 +2639,13 @@ Array [ "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(7)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(7)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(5)@jpeg", }, Object { "alt": "../dev-env/src/seed/img/key-alt.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(7)/cids(8)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(7)/cids(8)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(8)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(8)@jpeg", }, ], }, @@ -2673,9 +2673,9 @@ Array [ "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(11)", - "val": "kind", + "val": "test-label-3", }, ], "uri": "record(11)", @@ -2697,9 +2697,9 @@ Array [ "cid": "cids(7)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(8)", - "val": "kind", + "val": "test-label-3", }, ], "likeCount": 2, @@ -2774,9 +2774,9 @@ Array [ "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(11)", - "val": "kind", + "val": "test-label-3", }, ], "likeCount": 0, @@ -2898,13 +2898,13 @@ Array [ "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(2)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(2)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(2)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(2)@jpeg", }, Object { "alt": "../dev-env/src/seed/img/key-alt.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(3)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(3)@jpeg", }, ], }, @@ -2929,9 +2929,9 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(4)", - "val": "kind", + "val": "test-label-3", }, ], "uri": "record(4)", @@ -2954,9 +2954,9 @@ Array [ "cid": "cids(1)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(2)", - "val": "kind", + "val": "test-label-3", }, ], "uri": "record(2)", @@ -3055,7 +3055,7 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", @@ -3139,7 +3139,7 @@ Array [ "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(10)", "val": "test-label", }, @@ -3147,7 +3147,7 @@ Array [ "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(10)", "val": "test-label-2", }, @@ -3192,7 +3192,7 @@ Array [ "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", @@ -3279,7 +3279,7 @@ Array [ "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", @@ -3327,7 +3327,7 @@ Array [ "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", @@ -3404,7 +3404,7 @@ Array [ "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(10)", "val": "test-label", }, @@ -3412,7 +3412,7 @@ Array [ "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(10)", "val": "test-label-2", }, @@ -3458,7 +3458,7 @@ Array [ "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", @@ -3506,7 +3506,7 @@ Array [ "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", @@ -3556,7 +3556,7 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", @@ -3623,9 +3623,9 @@ Array [ "cid": "cids(1)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(2)", - "val": "kind", + "val": "test-label-3", }, ], "uri": "record(2)", @@ -3710,7 +3710,7 @@ Array [ "cid": "cids(11)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(13)", "val": "test-label", }, @@ -3767,7 +3767,7 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", @@ -3833,13 +3833,13 @@ Array [ "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(2)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(2)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(2)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(2)@jpeg", }, Object { "alt": "../dev-env/src/seed/img/key-alt.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(3)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(3)@jpeg", }, ], }, @@ -3865,9 +3865,9 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(4)", - "val": "kind", + "val": "test-label-3", }, ], "uri": "record(4)", @@ -3889,9 +3889,9 @@ Array [ "cid": "cids(1)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(2)", - "val": "kind", + "val": "test-label-3", }, ], "likeCount": 2, @@ -3964,9 +3964,9 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(4)", - "val": "kind", + "val": "test-label-3", }, ], "likeCount": 0, @@ -3988,7 +3988,7 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", @@ -4088,13 +4088,13 @@ Array [ "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(2)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(2)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(2)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(2)@jpeg", }, Object { "alt": "../dev-env/src/seed/img/key-alt.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(3)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(3)@jpeg", }, ], }, @@ -4120,9 +4120,9 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(2)", - "val": "kind", + "val": "test-label-3", }, ], "uri": "record(2)", @@ -4145,9 +4145,9 @@ Array [ "cid": "cids(1)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(1)", - "val": "kind", + "val": "test-label-3", }, ], "uri": "record(1)", @@ -4247,7 +4247,7 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", @@ -4332,7 +4332,7 @@ Array [ "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(10)", "val": "test-label", }, @@ -4340,7 +4340,7 @@ Array [ "cid": "cids(9)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(10)", "val": "test-label-2", }, @@ -4385,7 +4385,7 @@ Array [ "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", @@ -4471,7 +4471,7 @@ Array [ "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", @@ -4519,7 +4519,7 @@ Array [ "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", @@ -4569,7 +4569,7 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", @@ -4634,9 +4634,9 @@ Array [ "cid": "cids(1)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(1)", - "val": "kind", + "val": "test-label-3", }, ], "uri": "record(1)", @@ -4721,7 +4721,7 @@ Array [ "cid": "cids(11)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(13)", "val": "test-label", }, @@ -4750,7 +4750,7 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", @@ -4815,13 +4815,13 @@ Array [ "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(2)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(2)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(2)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(2)@jpeg", }, Object { "alt": "../dev-env/src/seed/img/key-alt.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(3)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(3)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(3)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(3)@jpeg", }, ], }, @@ -4848,9 +4848,9 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(2)", - "val": "kind", + "val": "test-label-3", }, ], "uri": "record(2)", @@ -4872,9 +4872,9 @@ Array [ "cid": "cids(1)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "record(1)", - "val": "kind", + "val": "test-label-3", }, ], "likeCount": 2, @@ -4928,7 +4928,7 @@ Array [ Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(7)/cids(5)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(6)/cids(5)@jpeg", "did": "user(1)", "displayName": "ali", "handle": "alice.test", @@ -5096,7 +5096,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(4)", "val": "test-label", }, @@ -5104,7 +5104,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(4)", "val": "test-label-2", }, @@ -5289,7 +5289,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(4)", "val": "test-label", }, @@ -5297,7 +5297,7 @@ Array [ "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(4)", "val": "test-label-2", }, @@ -5484,7 +5484,7 @@ Array [ "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(6)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -5501,13 +5501,13 @@ Array [ "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(7)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(7)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(5)@jpeg", }, Object { "alt": "../dev-env/src/seed/img/key-alt.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(7)/cids(9)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(7)/cids(9)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(9)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(9)@jpeg", }, ], }, @@ -5533,9 +5533,9 @@ Array [ "cid": "cids(10)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(12)", - "val": "kind", + "val": "test-label-3", }, ], "uri": "record(12)", @@ -5558,9 +5558,9 @@ Array [ "cid": "cids(8)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(11)", - "val": "kind", + "val": "test-label-3", }, ], "uri": "record(11)", @@ -5689,9 +5689,9 @@ Array [ "cid": "cids(10)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(5)", + "src": "did:example:labeler", "uri": "record(12)", - "val": "kind", + "val": "test-label-3", }, ], "likeCount": 0, diff --git a/packages/bsky/tests/views/actor-search.test.ts b/packages/bsky/tests/views/actor-search.test.ts index c0e862de249..e7c4390edc2 100644 --- a/packages/bsky/tests/views/actor-search.test.ts +++ b/packages/bsky/tests/views/actor-search.test.ts @@ -19,11 +19,11 @@ describe.skip('pds actor search views', () => { sc = network.getSeedClient() await wait(50) // allow pending sub to be established - await network.bsky.ingester.sub.destroy() + await network.bsky.sub.destroy() await usersBulkSeed(sc) // Skip did/handle resolution for expediency - const db = network.bsky.ctx.db.getPrimary() + const { db } = network.bsky const now = new Date().toISOString() await db.db .insertInto('actor') @@ -38,9 +38,8 @@ describe.skip('pds actor search views', () => { .execute() // Process remaining profiles - network.bsky.ingester.sub.resume() + network.bsky.sub.run() await network.processAll(50000) - await network.bsky.processAll() headers = await network.serviceHeaders(Object.values(sc.dids)[0]) }) @@ -238,22 +237,9 @@ describe.skip('pds actor search views', () => { }) it('search blocks by actor takedown', async () => { - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: sc.dids['cara-wiegand69.test'], - }, - takedown: { - applied: true, - ref: 'test', - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ) + await network.bsky.server.ctx.dataplane.takedownActor({ + did: sc.dids['cara-wiegand69.test'], + }) const result = await agent.api.app.bsky.actor.searchActorsTypeahead( { term: 'car' }, { headers }, diff --git a/packages/bsky/tests/views/author-feed.test.ts b/packages/bsky/tests/views/author-feed.test.ts index b4644d49f67..160608744a6 100644 --- a/packages/bsky/tests/views/author-feed.test.ts +++ b/packages/bsky/tests/views/author-feed.test.ts @@ -146,22 +146,9 @@ describe('pds author feed views', () => { expect(preBlock.feed.length).toBeGreaterThan(0) - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: alice, - }, - takedown: { - applied: true, - ref: 'test', - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.takedownActor({ + did: alice, + }) const attemptAsUser = agent.api.app.bsky.feed.getAuthorFeed( { actor: alice }, @@ -173,25 +160,12 @@ describe('pds author feed views', () => { { 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( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: alice, - }, - takedown: { - applied: false, - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.untakedownActor({ + did: alice, + }) }) it('blocked by record takedown.', async () => { @@ -204,23 +178,9 @@ describe('pds author feed views', () => { const post = preBlock.feed[0].post - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.repo.strongRef', - uri: post.uri, - cid: post.cid, - }, - takedown: { - applied: true, - ref: 'test', - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.takedownRecord({ + recordUri: post.uri, + }) const [{ data: postBlockAsUser }, { data: postBlockAsAdmin }] = await Promise.all([ @@ -244,22 +204,9 @@ describe('pds author feed views', () => { ) // Cleanup - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.repo.strongRef', - uri: post.uri, - cid: post.cid, - }, - takedown: { - applied: false, - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.untakedownRecord({ + recordUri: post.uri, + }) }) it('can filter by posts_with_media', async () => { diff --git a/packages/bsky/tests/views/blocks.test.ts b/packages/bsky/tests/views/blocks.test.ts index 2f45477c664..ceff6f57392 100644 --- a/packages/bsky/tests/views/blocks.test.ts +++ b/packages/bsky/tests/views/blocks.test.ts @@ -111,7 +111,7 @@ describe('pds views with blocking', () => { expect(forSnapshot(thread)).toMatchSnapshot() }) - it('loads blocked reply as anchor with no parent', async () => { + it('loads blocked reply as anchor with blocked parent', async () => { const { data: thread } = await agent.api.app.bsky.feed.getPostThread( { depth: 1, uri: carolReplyToDan.ref.uriStr }, { headers: await network.serviceHeaders(alice) }, @@ -120,7 +120,10 @@ describe('pds views with blocking', () => { throw new Error('Expected thread view post') } expect(thread.thread.post.uri).toEqual(carolReplyToDan.ref.uriStr) - expect(thread.thread.parent).toBeUndefined() + expect(thread.thread.parent).toMatchObject({ + $type: 'app.bsky.feed.defs#blockedPost', + uri: sc.posts[dan][0].ref.uriStr, + }) }) it('blocks thread parent', async () => { @@ -409,10 +412,9 @@ describe('pds views with blocking', () => { { headers: await network.serviceHeaders(alice) }, ) assert(isThreadViewPost(unblock.thread)) - expect(unblock.thread.replies?.map(getThreadPostUri)).toEqual([ - carolReplyToDan.ref.uriStr, - aliceReplyToDan.ref.uriStr, - ]) + expect(unblock.thread.replies?.map(getThreadPostUri).sort()).toEqual( + [aliceReplyToDan.ref.uriStr, carolReplyToDan.ref.uriStr].sort(), + ) // block then reply danBlockCarol = await pdsAgent.api.app.bsky.graph.block.create( diff --git a/packages/bsky/tests/views/follows.test.ts b/packages/bsky/tests/views/follows.test.ts index d0f640c6813..2331a0e990c 100644 --- a/packages/bsky/tests/views/follows.test.ts +++ b/packages/bsky/tests/views/follows.test.ts @@ -18,7 +18,6 @@ describe('pds follow views', () => { sc = network.getSeedClient() await followsSeed(sc) await network.processAll() - await network.bsky.processAll() alice = sc.dids.alice }) @@ -119,22 +118,9 @@ describe('pds follow views', () => { }) it('blocks followers by actor takedown', async () => { - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: sc.dids.dan, - }, - takedown: { - applied: true, - ref: 'test', - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.takedownActor({ + did: sc.dids.dan, + }) const aliceFollowers = await agent.api.app.bsky.graph.getFollowers( { actor: sc.dids.alice }, @@ -145,21 +131,9 @@ describe('pds follow views', () => { sc.dids.dan, ) - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: sc.dids.dan, - }, - takedown: { - applied: false, - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.untakedownActor({ + did: sc.dids.dan, + }) }) it('fetches follows', async () => { @@ -252,22 +226,9 @@ describe('pds follow views', () => { }) it('blocks follows by actor takedown', async () => { - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: sc.dids.dan, - }, - takedown: { - applied: true, - ref: 'test', - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.takedownActor({ + did: sc.dids.dan, + }) const aliceFollows = await agent.api.app.bsky.graph.getFollows( { actor: sc.dids.alice }, @@ -278,30 +239,18 @@ describe('pds follow views', () => { sc.dids.dan, ) - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: sc.dids.dan, - }, - takedown: { - applied: false, - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.untakedownActor({ + did: sc.dids.dan, + }) }) it('fetches relationships between users', async () => { const res = await agent.api.app.bsky.graph.getRelationships({ actor: sc.dids.bob, - others: [sc.dids.alice, sc.dids.bob, sc.dids.carol, 'did:example:fake'], + others: [sc.dids.alice, sc.dids.bob, sc.dids.carol], }) expect(res.data.actor).toEqual(sc.dids.bob) - expect(res.data.relationships.length).toBe(4) + expect(res.data.relationships.length).toBe(3) expect(forSnapshot(res.data)).toMatchSnapshot() }) }) diff --git a/packages/bsky/tests/views/list-feed.test.ts b/packages/bsky/tests/views/list-feed.test.ts index 4951a6d6a23..f7b68b8db7a 100644 --- a/packages/bsky/tests/views/list-feed.test.ts +++ b/packages/bsky/tests/views/list-feed.test.ts @@ -111,22 +111,9 @@ describe('list feed views', () => { }) it('blocks posts by actor takedown', async () => { - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: bob, - }, - takedown: { - applied: true, - ref: 'test', - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.takedownActor({ + did: bob, + }) const res = await agent.api.app.bsky.feed.getListFeed({ list: listRef.uriStr, @@ -135,42 +122,16 @@ describe('list feed views', () => { expect(hasBob).toBe(false) // Cleanup - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: bob, - }, - takedown: { - applied: false, - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.untakedownActor({ + did: bob, + }) }) it('blocks posts by record takedown.', async () => { const postRef = sc.replies[bob][0].ref // Post and reply parent - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.repo.strongRef', - uri: postRef.uriStr, - cid: postRef.cidStr, - }, - takedown: { - applied: true, - ref: 'test', - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.takedownRecord({ + recordUri: postRef.uriStr, + }) const res = await agent.api.app.bsky.feed.getListFeed({ list: listRef.uriStr, @@ -181,21 +142,8 @@ describe('list feed views', () => { expect(hasPost).toBe(false) // Cleanup - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.repo.strongRef', - uri: postRef.uriStr, - cid: postRef.cidStr, - }, - takedown: { - applied: false, - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.untakedownRecord({ + recordUri: postRef.uriStr, + }) }) }) diff --git a/packages/bsky/tests/views/mute-lists.test.ts b/packages/bsky/tests/views/mute-lists.test.ts index c366b9bf390..3f90586f681 100644 --- a/packages/bsky/tests/views/mute-lists.test.ts +++ b/packages/bsky/tests/views/mute-lists.test.ts @@ -92,7 +92,6 @@ describe('bsky views with mutes from mute lists', () => { }) it('uses a list for mutes', async () => { - // @TODO proxy through appview await agent.api.app.bsky.graph.muteActorList( { list: listUri, @@ -196,7 +195,6 @@ describe('bsky views with mutes from mute lists', () => { // unfollow so they _would_ show up in suggestions if not for mute await sc.unfollow(dan, carol) await network.processAll() - await network.bsky.processAll() const res = await agent.api.app.bsky.actor.getSuggestions( { diff --git a/packages/bsky/tests/views/mutes.test.ts b/packages/bsky/tests/views/mutes.test.ts index 8e26770ef23..b9d276b975e 100644 --- a/packages/bsky/tests/views/mutes.test.ts +++ b/packages/bsky/tests/views/mutes.test.ts @@ -228,6 +228,6 @@ describe('mute views', () => { encoding: 'application/json', }, ) - await expect(promise).rejects.toThrow('Cannot mute oneself') + await expect(promise).rejects.toThrow() // @TODO check error message w/ grpc error passthru }) }) diff --git a/packages/bsky/tests/views/notifications.test.ts b/packages/bsky/tests/views/notifications.test.ts index 376ead163fc..d9b88d0bffe 100644 --- a/packages/bsky/tests/views/notifications.test.ts +++ b/packages/bsky/tests/views/notifications.test.ts @@ -19,7 +19,6 @@ describe('notification views', () => { sc = network.getSeedClient() await basicSeed(sc) await network.processAll() - await network.bsky.processAll() alice = sc.dids.alice }) @@ -72,7 +71,6 @@ describe('notification views', () => { 'indeed', ) await network.processAll() - await network.bsky.processAll() const notifCountAlice = await agent.api.app.bsky.notification.getUnreadCount( @@ -96,7 +94,6 @@ describe('notification views', () => { await sc.deletePost(sc.dids.alice, root.ref.uri) const second = await sc.reply(sc.dids.carol, root.ref, first.ref, 'second') await network.processAll() - await network.bsky.processAll() const notifsAlice = await agent.api.app.bsky.notification.listNotifications( {}, @@ -132,7 +129,7 @@ describe('notification views', () => { expect(notifs.length).toBe(13) const readStates = notifs.map((notif) => notif.isRead) - expect(readStates).toEqual(notifs.map(() => false)) + expect(readStates).toEqual(notifs.map((_, i) => i !== 0)) // only first appears unread expect(forSnapshot(sort(notifs))).toMatchSnapshot() }) @@ -224,7 +221,7 @@ describe('notification views', () => { expect(notifs.length).toBe(13) const readStates = notifs.map((notif) => notif.isRead) - expect(readStates).toEqual(notifs.map((n) => n.indexedAt <= seenAt)) + expect(readStates).toEqual(notifs.map((n) => n.indexedAt < seenAt)) // reset last-seen await agent.api.app.bsky.notification.updateSeen( { seenAt: new Date(0).toISOString() }, @@ -240,23 +237,9 @@ describe('notification views', () => { const postRef2 = sc.posts[sc.dids.dan][1].ref // Mention await Promise.all( [postRef1, postRef2].map((postRef) => - agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.repo.strongRef', - uri: postRef.uriStr, - cid: postRef.cidStr, - }, - takedown: { - applied: true, - ref: 'test', - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ), + network.bsky.ctx.dataplane.takedownRecord({ + recordUri: postRef.uriStr, + }), ), ) @@ -277,22 +260,9 @@ describe('notification views', () => { // Cleanup await Promise.all( [postRef1, postRef2].map((postRef) => - agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.repo.strongRef', - uri: postRef.uriStr, - cid: postRef.cidStr, - }, - takedown: { - applied: false, - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ), + network.bsky.ctx.dataplane.untakedownRecord({ + recordUri: postRef.uriStr, + }), ), ) }) @@ -300,7 +270,7 @@ describe('notification views', () => { it('fails open on clearly bad cursor.', async () => { const { data: notifs } = await agent.api.app.bsky.notification.listNotifications( - { cursor: 'bad' }, + { cursor: '90210::bafycid' }, { headers: await network.serviceHeaders(alice) }, ) expect(notifs).toEqual({ notifications: [] }) diff --git a/packages/bsky/tests/views/profile.test.ts b/packages/bsky/tests/views/profile.test.ts index ddd484b3b31..8c83c3e49aa 100644 --- a/packages/bsky/tests/views/profile.test.ts +++ b/packages/bsky/tests/views/profile.test.ts @@ -183,93 +183,20 @@ describe('pds profile views', () => { }) it('blocked by actor takedown', async () => { - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: alice, - }, - takedown: { - applied: true, - ref: 'test', - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ) - const promise = agent.api.app.bsky.actor.getProfile( - { actor: alice }, - { headers: await network.serviceHeaders(bob) }, - ) - - await expect(promise).rejects.toThrow('Account has been taken down') - - // Cleanup - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: alice, - }, - takedown: { - applied: false, - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ) - }) - - it('blocked by actor suspension', async () => { - await pdsAgent.api.com.atproto.admin.emitModerationEvent( - { - event: { - $type: 'com.atproto.admin.defs#modEventTakedown', - durationInHours: 1, - }, - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: alice, - }, - createdBy: 'did:example:admin', - reason: 'Y', - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ) - await network.processAll() + await network.bsky.ctx.dataplane.takedownActor({ + did: alice, + }) const promise = agent.api.app.bsky.actor.getProfile( { actor: alice }, { headers: await network.serviceHeaders(bob) }, ) - await expect(promise).rejects.toThrow( - 'Account has been temporarily suspended', - ) + await expect(promise).rejects.toThrow('Account has been suspended') // Cleanup - await pdsAgent.api.com.atproto.admin.emitModerationEvent( - { - event: { $type: 'com.atproto.admin.defs#modEventReverseTakedown' }, - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: alice, - }, - createdBy: 'did:example:admin', - reason: 'Y', - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ) - await network.processAll() + await network.bsky.ctx.dataplane.untakedownActor({ + did: alice, + }) }) async function updateProfile(did: string, record: Record) { diff --git a/packages/bsky/tests/views/suggested-follows.test.ts b/packages/bsky/tests/views/suggested-follows.test.ts index ff9bec4539c..5d39f3f90ef 100644 --- a/packages/bsky/tests/views/suggested-follows.test.ts +++ b/packages/bsky/tests/views/suggested-follows.test.ts @@ -16,7 +16,6 @@ describe('suggested follows', () => { sc = network.getSeedClient() await likesSeed(sc) await network.processAll() - await network.bsky.processAll() const suggestions = [ { did: sc.dids.alice, order: 1 }, @@ -26,9 +25,8 @@ describe('suggested follows', () => { { did: sc.dids.fred, order: 5 }, { did: sc.dids.gina, order: 6 }, ] - await network.bsky.ctx.db - .getPrimary() - .db.insertInto('suggested_follow') + await network.bsky.db.db + .insertInto('suggested_follow') .values(suggestions) .execute() }) diff --git a/packages/bsky/tests/views/suggestions.test.ts b/packages/bsky/tests/views/suggestions.test.ts index bcae515ffb3..49bc5858a34 100644 --- a/packages/bsky/tests/views/suggestions.test.ts +++ b/packages/bsky/tests/views/suggestions.test.ts @@ -15,7 +15,6 @@ describe('pds user search views', () => { sc = network.getSeedClient() await basicSeed(sc) await network.processAll() - await network.bsky.processAll() const suggestions = [ { did: sc.dids.alice, order: 1 }, @@ -24,9 +23,8 @@ describe('pds user search views', () => { { did: sc.dids.dan, order: 4 }, ] - await network.bsky.ctx.db - .getPrimary() - .db.insertInto('suggested_follow') + await network.bsky.db.db + .insertInto('suggested_follow') .values(suggestions) .execute() }) @@ -61,21 +59,21 @@ describe('pds user search views', () => { it('paginates', async () => { const result1 = await agent.api.app.bsky.actor.getSuggestions( - { limit: 1 }, + { limit: 2 }, { headers: await network.serviceHeaders(sc.dids.carol) }, ) expect(result1.data.actors.length).toBe(1) expect(result1.data.actors[0].handle).toEqual('bob.test') const result2 = await agent.api.app.bsky.actor.getSuggestions( - { limit: 1, cursor: result1.data.cursor }, + { limit: 2, cursor: result1.data.cursor }, { headers: await network.serviceHeaders(sc.dids.carol) }, ) expect(result2.data.actors.length).toBe(1) expect(result2.data.actors[0].handle).toEqual('dan.test') const result3 = await agent.api.app.bsky.actor.getSuggestions( - { limit: 1, cursor: result2.data.cursor }, + { limit: 2, cursor: result2.data.cursor }, { headers: await network.serviceHeaders(sc.dids.carol) }, ) expect(result3.data.actors.length).toBe(0) @@ -110,9 +108,8 @@ describe('pds user search views', () => { subjectType: 'feed', }, ] - await network.bsky.ctx.db - .getPrimary() - .db.insertInto('tagged_suggestion') + await network.bsky.db.db + .insertInto('tagged_suggestion') .values(suggestions) .execute() const res = await agent.api.app.bsky.unspecced.getTaggedSuggestions() diff --git a/packages/bsky/tests/views/thread.test.ts b/packages/bsky/tests/views/thread.test.ts index 88f7db4c573..c3496f7cf50 100644 --- a/packages/bsky/tests/views/thread.test.ts +++ b/packages/bsky/tests/views/thread.test.ts @@ -113,7 +113,6 @@ describe('pds thread views', () => { ) indexes.aliceReplyReply = sc.replies[alice].length - 1 await network.processAll() - await network.bsky.processAll() const thread1 = await agent.api.app.bsky.feed.getPostThread( { uri: sc.posts[alice][indexes.aliceRoot].ref.uriStr }, @@ -123,7 +122,6 @@ describe('pds thread views', () => { await sc.deletePost(bob, sc.replies[bob][indexes.bobReply].ref.uri) await network.processAll() - await network.bsky.processAll() const thread2 = await agent.api.app.bsky.feed.getPostThread( { uri: sc.posts[alice][indexes.aliceRoot].ref.uriStr }, @@ -138,6 +136,56 @@ describe('pds thread views', () => { expect(forSnapshot(thread3.data.thread)).toMatchSnapshot() }) + it('omits parents and replies w/ different root than anchor post.', async () => { + const badRoot = sc.posts[alice][0] + const goodRoot = await sc.post(alice, 'good root') + const goodReply1 = await sc.reply( + alice, + goodRoot.ref, + goodRoot.ref, + 'good reply 1', + ) + const goodReply2 = await sc.reply( + alice, + goodRoot.ref, + goodReply1.ref, + 'good reply 2', + ) + const badReply = await sc.reply( + alice, + badRoot.ref, + goodReply1.ref, + 'bad reply', + ) + await network.processAll() + // good reply doesn't have replies w/ different root + const { data: goodReply1Thread } = + await agent.api.app.bsky.feed.getPostThread( + { uri: goodReply1.ref.uriStr }, + { headers: await network.serviceHeaders(alice) }, + ) + assert(isThreadViewPost(goodReply1Thread.thread)) + assert(isThreadViewPost(goodReply1Thread.thread.parent)) + expect(goodReply1Thread.thread.parent.post.uri).toEqual(goodRoot.ref.uriStr) + expect( + goodReply1Thread.thread.replies?.map((r) => { + assert(isThreadViewPost(r)) + return r.post.uri + }), + ).toEqual([ + goodReply2.ref.uriStr, // does not contain badReply + ]) + expect(goodReply1Thread.thread.parent.replies).toBeUndefined() + // bad reply doesn't have a parent, which would have a different root + const { data: badReplyThread } = + await agent.api.app.bsky.feed.getPostThread( + { uri: badReply.ref.uriStr }, + { headers: await network.serviceHeaders(alice) }, + ) + assert(isThreadViewPost(badReplyThread.thread)) + expect(badReplyThread.thread.parent).toBeUndefined() // is not goodReply1 + }) + it('reflects self-labels', async () => { const { data: thread } = await agent.api.app.bsky.feed.getPostThread( { uri: sc.posts[alice][0].ref.uriStr }, @@ -163,22 +211,9 @@ describe('pds thread views', () => { describe('takedown', () => { it('blocks post by actor', async () => { - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: alice, - }, - takedown: { - applied: true, - ref: 'test', - }, - }, - { - encoding: 'application/json', - headers: network.bsky.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.takedownActor({ + did: alice, + }) // Same as shallow post thread test, minus alice const promise = agent.api.app.bsky.feed.getPostThread( @@ -191,40 +226,15 @@ describe('pds thread views', () => { ) // Cleanup - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: alice, - }, - takedown: { - applied: false, - }, - }, - { - encoding: 'application/json', - headers: network.bsky.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.untakedownActor({ + did: alice, + }) }) it('blocks replies by actor', async () => { - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: carol, - }, - takedown: { - applied: true, - ref: 'test', - }, - }, - { - encoding: 'application/json', - headers: network.bsky.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.takedownActor({ + did: carol, + }) // Same as deep post thread test, minus carol const thread = await agent.api.app.bsky.feed.getPostThread( @@ -235,40 +245,15 @@ describe('pds thread views', () => { expect(forSnapshot(thread.data.thread)).toMatchSnapshot() // Cleanup - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: carol, - }, - takedown: { - applied: false, - }, - }, - { - encoding: 'application/json', - headers: network.bsky.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.untakedownActor({ + did: carol, + }) }) it('blocks ancestors by actor', async () => { - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: bob, - }, - takedown: { - applied: true, - ref: 'test', - }, - }, - { - encoding: 'application/json', - headers: network.bsky.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.takedownActor({ + did: bob, + }) // Same as ancestor post thread test, minus bob const thread = await agent.api.app.bsky.feed.getPostThread( @@ -279,42 +264,16 @@ describe('pds thread views', () => { expect(forSnapshot(thread.data.thread)).toMatchSnapshot() // Cleanup - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: bob, - }, - takedown: { - applied: false, - }, - }, - { - encoding: 'application/json', - headers: network.bsky.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.untakedownActor({ + did: bob, + }) }) it('blocks post by record', async () => { const postRef = sc.posts[alice][1].ref - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.repo.strongRef', - uri: postRef.uriStr, - cid: postRef.cidStr, - }, - takedown: { - applied: true, - ref: 'test', - }, - }, - { - encoding: 'application/json', - headers: network.bsky.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.takedownRecord({ + recordUri: postRef.uriStr, + }) const promise = agent.api.app.bsky.feed.getPostThread( { depth: 1, uri: postRef.uriStr }, @@ -326,22 +285,9 @@ describe('pds thread views', () => { ) // Cleanup - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.repo.strongRef', - uri: postRef.uriStr, - cid: postRef.cidStr, - }, - takedown: { - applied: false, - }, - }, - { - encoding: 'application/json', - headers: network.bsky.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.untakedownRecord({ + recordUri: postRef.uriStr, + }) }) it('blocks ancestors by record', async () => { @@ -352,23 +298,9 @@ describe('pds thread views', () => { const parent = threadPreTakedown.data.thread.parent?.['post'] - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.repo.strongRef', - uri: parent.uri, - cid: parent.cid, - }, - takedown: { - applied: true, - ref: 'test', - }, - }, - { - encoding: 'application/json', - headers: network.bsky.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.takedownRecord({ + recordUri: parent.uri, + }) // Same as ancestor post thread test, minus parent post const thread = await agent.api.app.bsky.feed.getPostThread( @@ -379,22 +311,9 @@ describe('pds thread views', () => { expect(forSnapshot(thread.data.thread)).toMatchSnapshot() // Cleanup - await agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.repo.strongRef', - uri: parent.uri, - cid: parent.cid, - }, - takedown: { - applied: false, - }, - }, - { - encoding: 'application/json', - headers: network.bsky.adminAuthHeaders(), - }, - ) + await network.bsky.ctx.dataplane.untakedownRecord({ + recordUri: parent.uri, + }) }) it('blocks replies by record', async () => { @@ -407,23 +326,9 @@ describe('pds thread views', () => { await Promise.all( [post1, post2].map((post) => - agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.repo.strongRef', - uri: post.uri, - cid: post.cid, - }, - takedown: { - applied: true, - ref: 'test', - }, - }, - { - encoding: 'application/json', - headers: network.bsky.adminAuthHeaders(), - }, - ), + network.bsky.ctx.dataplane.takedownRecord({ + recordUri: post.uri, + }), ), ) @@ -438,25 +343,9 @@ describe('pds thread views', () => { // Cleanup await Promise.all( [post1, post2].map((post) => - agent.api.com.atproto.admin.updateSubjectStatus( - { - event: { - $type: 'com.atproto.admin.defs#modEventReverseTakedown', - }, - subject: { - $type: 'com.atproto.repo.strongRef', - uri: post.uri, - cid: post.cid, - }, - takedown: { - applied: false, - }, - }, - { - encoding: 'application/json', - headers: network.bsky.adminAuthHeaders(), - }, - ), + network.bsky.ctx.dataplane.untakedownRecord({ + recordUri: post.uri, + }), ), ) }) diff --git a/packages/bsky/tests/views/threadgating.test.ts b/packages/bsky/tests/views/threadgating.test.ts index feb10dfbadd..cd154cedd83 100644 --- a/packages/bsky/tests/views/threadgating.test.ts +++ b/packages/bsky/tests/views/threadgating.test.ts @@ -308,8 +308,9 @@ describe('views with thread gating', () => { assert(isThreadViewPost(reply1)) assert(isThreadViewPost(reply2)) expect(otherReplies.length).toEqual(0) - expect(reply1.post.uri).toEqual(danReply.ref.uriStr) - expect(reply2.post.uri).toEqual(aliceReply.ref.uriStr) + expect([reply1.post.uri, reply2.post.uri].sort()).toEqual( + [danReply.ref.uriStr, aliceReply.ref.uriStr].sort(), + ) }) it('applies gate for unknown list rule.', async () => { @@ -418,8 +419,9 @@ describe('views with thread gating', () => { assert(isThreadViewPost(reply1)) assert(isThreadViewPost(reply2)) expect(otherReplies.length).toEqual(0) - expect(reply1.post.uri).toEqual(danReply.ref.uriStr) - expect(reply2.post.uri).toEqual(aliceReply.ref.uriStr) + expect([reply1.post.uri, reply2.post.uri].sort()).toEqual( + [aliceReply.ref.uriStr, danReply.ref.uriStr].sort(), + ) }) it('applies gate for missing rules, takes no action.', async () => { diff --git a/packages/bsky/tests/views/timeline.test.ts b/packages/bsky/tests/views/timeline.test.ts index dd9b89535c8..f697f02e033 100644 --- a/packages/bsky/tests/views/timeline.test.ts +++ b/packages/bsky/tests/views/timeline.test.ts @@ -2,8 +2,10 @@ import assert from 'assert' import AtpAgent from '@atproto/api' import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env' import { forSnapshot, getOriginator, paginateAll } from '../_util' -import { FeedAlgorithm } from '../../src/api/app/bsky/util/feed' import { FeedViewPost } from '../../src/lexicon/types/app/bsky/feed/defs' +import { Database } from '../../src' + +const REVERSE_CHRON = 'reverse-chronological' describe('timeline views', () => { let network: TestNetwork @@ -28,26 +30,18 @@ describe('timeline views', () => { bob = sc.dids.bob carol = sc.dids.carol dan = sc.dids.dan - // Label posts as "kind" to check labels on embed views - const labelPostA = sc.posts[bob][0].ref - const labelPostB = sc.posts[carol][0].ref - await network.bsky.ctx.services - .label(network.bsky.ctx.db.getPrimary()) - .formatAndCreate( - network.ozone.ctx.cfg.service.did, - labelPostA.uriStr, - labelPostA.cidStr, - { create: ['kind'] }, - ) - await network.bsky.ctx.services - .label(network.bsky.ctx.db.getPrimary()) - .formatAndCreate( - network.ozone.ctx.cfg.service.did, - labelPostB.uriStr, - labelPostB.cidStr, - { create: ['kind'] }, - ) - await network.bsky.processAll() + // covers label hydration on embeds + const { db } = network.bsky + await createLabel(db, { + val: 'test-label-3', + uri: sc.posts[bob][0].ref.uriStr, + cid: sc.posts[bob][0].ref.cidStr, + }) + await createLabel(db, { + val: 'test-label-3', + uri: sc.posts[carol][0].ref.uriStr, + cid: sc.posts[carol][0].ref.cidStr, + }) }) afterAll(async () => { @@ -67,7 +61,7 @@ describe('timeline views', () => { } const aliceTL = await agent.api.app.bsky.feed.getTimeline( - { algorithm: FeedAlgorithm.ReverseChronological }, + { algorithm: REVERSE_CHRON }, { headers: await network.serviceHeaders(alice), }, @@ -77,7 +71,7 @@ describe('timeline views', () => { aliceTL.data.feed.forEach(expectOriginatorFollowedBy(alice)) const bobTL = await agent.api.app.bsky.feed.getTimeline( - { algorithm: FeedAlgorithm.ReverseChronological }, + { algorithm: REVERSE_CHRON }, { headers: await network.serviceHeaders(bob), }, @@ -87,7 +81,7 @@ describe('timeline views', () => { bobTL.data.feed.forEach(expectOriginatorFollowedBy(bob)) const carolTL = await agent.api.app.bsky.feed.getTimeline( - { algorithm: FeedAlgorithm.ReverseChronological }, + { algorithm: REVERSE_CHRON }, { headers: await network.serviceHeaders(carol), }, @@ -97,7 +91,7 @@ describe('timeline views', () => { carolTL.data.feed.forEach(expectOriginatorFollowedBy(carol)) const danTL = await agent.api.app.bsky.feed.getTimeline( - { algorithm: FeedAlgorithm.ReverseChronological }, + { algorithm: REVERSE_CHRON }, { headers: await network.serviceHeaders(dan), }, @@ -115,7 +109,7 @@ describe('timeline views', () => { }, ) const reverseChronologicalTL = await agent.api.app.bsky.feed.getTimeline( - { algorithm: FeedAlgorithm.ReverseChronological }, + { algorithm: REVERSE_CHRON }, { headers: await network.serviceHeaders(alice), }, @@ -128,7 +122,7 @@ describe('timeline views', () => { const paginator = async (cursor?: string) => { const res = await agent.api.app.bsky.feed.getTimeline( { - algorithm: FeedAlgorithm.ReverseChronological, + algorithm: REVERSE_CHRON, cursor, limit: 4, }, @@ -144,7 +138,7 @@ describe('timeline views', () => { const full = await agent.api.app.bsky.feed.getTimeline( { - algorithm: FeedAlgorithm.ReverseChronological, + algorithm: REVERSE_CHRON, }, { headers: await network.serviceHeaders(carol) }, ) @@ -196,27 +190,12 @@ describe('timeline views', () => { it('blocks posts, reposts, replies by actor takedown', async () => { await Promise.all( [bob, carol].map((did) => - agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did, - }, - takedown: { - applied: true, - ref: 'test', - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ), + network.bsky.ctx.dataplane.takedownActor({ did }), ), ) const aliceTL = await agent.api.app.bsky.feed.getTimeline( - { algorithm: FeedAlgorithm.ReverseChronological }, + { algorithm: REVERSE_CHRON }, { headers: await network.serviceHeaders(alice) }, ) @@ -225,21 +204,7 @@ describe('timeline views', () => { // Cleanup await Promise.all( [bob, carol].map((did) => - agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did, - }, - takedown: { - applied: false, - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ), + network.bsky.ctx.dataplane.untakedownActor({ did }), ), ) }) @@ -249,28 +214,14 @@ describe('timeline views', () => { const postRef2 = sc.replies[bob][0].ref // Post and reply parent await Promise.all( [postRef1, postRef2].map((postRef) => - agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.repo.strongRef', - uri: postRef.uriStr, - cid: postRef.cidStr, - }, - takedown: { - applied: true, - ref: 'test', - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ), + network.bsky.ctx.dataplane.takedownRecord({ + recordUri: postRef.uriStr, + }), ), ) const aliceTL = await agent.api.app.bsky.feed.getTimeline( - { algorithm: FeedAlgorithm.ReverseChronological }, + { algorithm: REVERSE_CHRON }, { headers: await network.serviceHeaders(alice) }, ) @@ -279,31 +230,35 @@ describe('timeline views', () => { // Cleanup await Promise.all( [postRef1, postRef2].map((postRef) => - agent.api.com.atproto.admin.updateSubjectStatus( - { - subject: { - $type: 'com.atproto.repo.strongRef', - uri: postRef.uriStr, - cid: postRef.cidStr, - }, - takedown: { - applied: false, - }, - }, - { - encoding: 'application/json', - headers: network.pds.adminAuthHeaders(), - }, - ), + network.bsky.ctx.dataplane.untakedownRecord({ + recordUri: postRef.uriStr, + }), ), ) }) it('fails open on clearly bad cursor.', async () => { const { data: timeline } = await agent.api.app.bsky.feed.getTimeline( - { cursor: 'bad' }, + { cursor: '90210::bafycid' }, { headers: await network.serviceHeaders(alice) }, ) expect(timeline).toEqual({ feed: [] }) }) }) + +const createLabel = async ( + db: Database, + opts: { uri: string; cid: string; val: string }, +) => { + await db.db + .insertInto('label') + .values({ + uri: opts.uri, + cid: opts.cid, + val: opts.val, + cts: new Date().toISOString(), + neg: false, + src: 'did:example:labeler', + }) + .execute() +} diff --git a/packages/bsky/tsconfig.json b/packages/bsky/tsconfig.json index 3f6ca1c27ec..45283e8f73b 100644 --- a/packages/bsky/tsconfig.json +++ b/packages/bsky/tsconfig.json @@ -11,11 +11,10 @@ { "path": "../api/tsconfig.build.json" }, { "path": "../common/tsconfig.build.json" }, { "path": "../crypto/tsconfig.build.json" }, - { "path": "../identifier/tsconfig.build.json" }, { "path": "../lexicon/tsconfig.build.json" }, { "path": "../lex-cli/tsconfig.build.json" }, { "path": "../repo/tsconfig.build.json" }, - { "path": "../uri/tsconfig.build.json" }, + { "path": "../syntax/tsconfig.build.json" }, { "path": "../xrpc-server/tsconfig.build.json" } ] } diff --git a/packages/common-web/src/arrays.ts b/packages/common-web/src/arrays.ts index 51598fc86f1..36c2f0dcb27 100644 --- a/packages/common-web/src/arrays.ts +++ b/packages/common-web/src/arrays.ts @@ -1,3 +1,10 @@ +export const keyBy = (arr: T[], key: string): Record => { + return arr.reduce((acc, cur) => { + acc[cur[key]] = cur + return acc + }, {} as Record) +} + export const mapDefined = ( arr: T[], fn: (obj: T) => S | undefined, diff --git a/packages/dev-env/src/bsky.ts b/packages/dev-env/src/bsky.ts index 4548cd45c41..461ef4d07df 100644 --- a/packages/dev-env/src/bsky.ts +++ b/packages/dev-env/src/bsky.ts @@ -1,23 +1,22 @@ -import assert from 'assert' import getPort from 'get-port' import * as ui8 from 'uint8arrays' import * as bsky from '@atproto/bsky' -import { DAY, HOUR, MINUTE, SECOND, wait } from '@atproto/common-web' import { AtpAgent } from '@atproto/api' -import { Secp256k1Keypair, randomIntFromSeed } from '@atproto/crypto' +import { Secp256k1Keypair } from '@atproto/crypto' import { Client as PlcClient } from '@did-plc/lib' import { BskyConfig } from './types' -import { uniqueLockId } from './util' -import { TestNetworkNoAppView } from './network-no-appview' import { ADMIN_PASSWORD, MOD_PASSWORD, TRIAGE_PASSWORD } from './const' +import { BackgroundQueue } from '@atproto/bsky/src/data-plane/server/background' export class TestBsky { constructor( public url: string, public port: number, + public db: bsky.Database, public server: bsky.BskyAppView, - public indexer: bsky.BskyIndexer, - public ingester: bsky.BskyIngester, + public dataplane: bsky.DataPlaneServer, + public bsync: bsky.MockBsync, + public sub: bsky.RepoSubscription, ) {} static async create(cfg: BskyConfig): Promise { @@ -34,39 +33,43 @@ export class TestBsky { signer: serviceKeypair, }) + // shared across server, ingester, and indexer in order to share pool, avoid too many pg connections. + const db = new bsky.Database({ + url: cfg.dbPostgresUrl, + schema: cfg.dbPostgresSchema, + poolSize: 10, + }) + + const dataplanePort = await getPort() + const dataplane = await bsky.DataPlaneServer.create( + db, + dataplanePort, + cfg.plcUrl, + ) + + const bsyncPort = await getPort() + const bsync = await bsky.MockBsync.create(db, bsyncPort) + const config = new bsky.ServerConfig({ - version: '0.0.0', + version: 'unknown', port, didPlcUrl: cfg.plcUrl, publicUrl: 'https://bsky.public.url', serverDid, - didCacheStaleTTL: HOUR, - didCacheMaxTTL: DAY, - labelCacheStaleTTL: 30 * SECOND, - labelCacheMaxTTL: MINUTE, + dataplaneUrls: [`http://localhost:${dataplanePort}`], + dataplaneHttpVersion: '1.1', + bsyncUrl: `http://localhost:${bsyncPort}`, + bsyncHttpVersion: '1.1', + courierUrl: 'https://fake.example', modServiceDid: cfg.modServiceDid ?? 'did:example:invalidMod', + labelsFromIssuerDids: ['did:example:labeler'], // this did is also used as the labeler in seeds ...cfg, - // Each test suite gets its own lock id for the repo subscription - adminPassword: ADMIN_PASSWORD, - moderatorPassword: MOD_PASSWORD, - triagePassword: TRIAGE_PASSWORD, - feedGenDid: 'did:example:feedGen', - rateLimitsEnabled: false, - }) - - // shared across server, ingester, and indexer in order to share pool, avoid too many pg connections. - const db = new bsky.DatabaseCoordinator({ - schema: cfg.dbPostgresSchema, - primary: { - url: cfg.dbPrimaryPostgresUrl, - poolSize: 10, - }, - replicas: [], + adminPasswords: [ADMIN_PASSWORD, MOD_PASSWORD, TRIAGE_PASSWORD], }) // Separate migration db in case migration changes some connection state that we need in the tests, e.g. "alter database ... set ..." - const migrationDb = new bsky.PrimaryDatabase({ - url: cfg.dbPrimaryPostgresUrl, + const migrationDb = new bsky.Database({ + url: cfg.dbPostgresUrl, schema: cfg.dbPostgresSchema, }) if (cfg.migration) { @@ -76,285 +79,52 @@ export class TestBsky { } await migrationDb.close() - const ns = cfg.dbPostgresSchema - ? await randomIntFromSeed(cfg.dbPostgresSchema, 1000000) - : undefined - assert(config.redisHost) - const redisCache = new bsky.Redis({ - host: config.redisHost, - namespace: `ns${ns}`, - db: 1, - }) - // api server const server = bsky.BskyAppView.create({ - db, - redis: redisCache, config, - imgInvalidator: cfg.imgInvalidator, signingKey: serviceKeypair, }) - // indexer - const indexerCfg = new bsky.IndexerConfig({ - version: '0.0.0', - serverDid, - didCacheStaleTTL: HOUR, - didCacheMaxTTL: DAY, - redisHost: cfg.redisHost, - dbPostgresUrl: cfg.dbPrimaryPostgresUrl, - dbPostgresSchema: cfg.dbPostgresSchema, - didPlcUrl: cfg.plcUrl, - labelerKeywords: { label_me: 'test-label', label_me_2: 'test-label-2' }, - imgUriEndpoint: 'img.example.com', - moderationPushUrl: - cfg.indexer?.moderationPushUrl ?? 'https://modservice.invalid', - indexerPartitionIds: [0], - indexerNamespace: `ns${ns}`, - indexerSubLockId: uniqueLockId(), - indexerPort: await getPort(), - ingesterPartitionCount: 1, - pushNotificationEndpoint: 'https://push.bsky.app/api/push', - ...(cfg.indexer ?? {}), - }) - assert(indexerCfg.redisHost) - const indexerRedis = new bsky.Redis({ - host: indexerCfg.redisHost, - namespace: `ns${ns}`, - }) - const indexer = bsky.BskyIndexer.create({ - cfg: indexerCfg, - db: db.getPrimary(), - redis: indexerRedis, - redisCache, - }) - // ingester - const ingesterCfg = new bsky.IngesterConfig({ - version: '0.0.0', - redisHost: cfg.redisHost, - dbPostgresUrl: cfg.dbPrimaryPostgresUrl, - dbPostgresSchema: cfg.dbPostgresSchema, - repoProvider: cfg.repoProvider, - labelProvider: cfg.labelProvider, - ingesterNamespace: `ns${ns}`, - ingesterSubLockId: uniqueLockId(), - ingesterPartitionCount: 1, - ...(cfg.ingester ?? {}), - }) - assert(ingesterCfg.redisHost) - const ingesterRedis = new bsky.Redis({ - host: ingesterCfg.redisHost, - namespace: ingesterCfg.ingesterNamespace, - }) - const ingester = bsky.BskyIngester.create({ - cfg: ingesterCfg, - db: db.getPrimary(), - redis: ingesterRedis, + const sub = new bsky.RepoSubscription({ + service: cfg.repoProvider, + db, + idResolver: dataplane.idResolver, + background: new BackgroundQueue(db), }) - await ingester.start() - await indexer.start() - await server.start() - // manually process labels in dev-env (in network.processAll) - ingester.ctx.labelSubscription?.destroy() + await server.start() + sub.run() - return new TestBsky(url, port, server, indexer, ingester) + return new TestBsky(url, port, db, server, dataplane, bsync, sub) } get ctx(): bsky.AppContext { return this.server.ctx } - get sub() { - return this.indexer.sub - } - getClient() { return new AtpAgent({ service: this.url }) } - adminAuth(role: 'admin' | 'moderator' | 'triage' = 'admin'): string { - const password = - role === 'triage' - ? this.ctx.cfg.triagePassword - : role === 'moderator' - ? this.ctx.cfg.moderatorPassword - : this.ctx.cfg.adminPassword + adminAuth(): string { + const [password] = this.ctx.cfg.adminPasswords return ( 'Basic ' + ui8.toString(ui8.fromString(`admin:${password}`, 'utf8'), 'base64pad') ) } - adminAuthHeaders(role?: 'admin' | 'moderator' | 'triage') { + adminAuthHeaders() { return { - authorization: this.adminAuth(role), + authorization: this.adminAuth(), } } - async processAll() { - await Promise.all([ - this.ctx.backgroundQueue.processAll(), - this.indexer.ctx.backgroundQueue.processAll(), - ]) - } - async close() { - await this.server.destroy({ skipDb: true, skipRedis: true }) - await this.ingester.destroy({ skipDb: true }) - await this.indexer.destroy() // closes shared db & redis - } -} - -// Below are used for tests just of component parts of the appview, i.e. ingester and indexers: - -export async function getIngester( - network: TestNetworkNoAppView, - opts: { name: string } & Partial, -) { - const { name, ...config } = opts - const ns = name ? await randomIntFromSeed(name, 1000000) : undefined - const cfg = new bsky.IngesterConfig({ - version: '0.0.0', - redisHost: process.env.REDIS_HOST || '', - dbPostgresUrl: process.env.DB_POSTGRES_URL || '', - dbPostgresSchema: `appview_${name}`, - repoProvider: network.pds.url.replace('http://', 'ws://'), - labelProvider: 'http://labeler.invalid', - ingesterSubLockId: uniqueLockId(), - ingesterPartitionCount: config.ingesterPartitionCount ?? 1, - ingesterNamespace: `ns${ns}`, - ...config, - }) - const db = new bsky.PrimaryDatabase({ - url: cfg.dbPostgresUrl, - schema: cfg.dbPostgresSchema, - }) - assert(cfg.redisHost) - const redis = new bsky.Redis({ - host: cfg.redisHost, - namespace: cfg.ingesterNamespace, - }) - await db.migrateToLatestOrThrow() - const ingester = await bsky.BskyIngester.create({ cfg, db, redis }) - await ingester.ctx.labelSubscription?.destroy() - return ingester -} - -// get multiple indexers for separate partitions, sharing db and redis instance. -export async function getIndexers( - network: TestNetworkNoAppView, - opts: Partial & { - name: string - partitionIdsByIndexer: number[][] - }, -): Promise { - const { name, ...config } = opts - const ns = name ? await randomIntFromSeed(name, 1000000) : undefined - const baseCfg: bsky.IndexerConfigValues = { - version: '0.0.0', - serverDid: 'did:example:bsky', - didCacheStaleTTL: HOUR, - didCacheMaxTTL: DAY, - labelerKeywords: { label_me: 'test-label', label_me_2: 'test-label-2' }, - redisHost: process.env.REDIS_HOST || '', - dbPostgresUrl: process.env.DB_POSTGRES_URL || '', - dbPostgresSchema: `appview_${name}`, - didPlcUrl: network.plc.url, - imgUriEndpoint: '', - indexerPartitionIds: [0], - indexerNamespace: `ns${ns}`, - ingesterPartitionCount: config.ingesterPartitionCount ?? 1, - moderationPushUrl: config.moderationPushUrl ?? 'https://modservice.invalid', - ...config, - } - const db = new bsky.PrimaryDatabase({ - url: baseCfg.dbPostgresUrl, - schema: baseCfg.dbPostgresSchema, - }) - assert(baseCfg.redisHost) - const redis = new bsky.Redis({ - host: baseCfg.redisHost, - namespace: baseCfg.indexerNamespace, - }) - const redisCache = new bsky.Redis({ - host: baseCfg.redisHost, - namespace: baseCfg.indexerNamespace, - db: 1, - }) - - const indexers = await Promise.all( - opts.partitionIdsByIndexer.map(async (indexerPartitionIds) => { - const cfg = new bsky.IndexerConfig({ - ...baseCfg, - indexerPartitionIds, - indexerSubLockId: uniqueLockId(), - indexerPort: await getPort(), - }) - return bsky.BskyIndexer.create({ cfg, db, redis, redisCache }) - }), - ) - await db.migrateToLatestOrThrow() - return { - db, - list: indexers, - async start() { - await Promise.all(indexers.map((indexer) => indexer.start())) - }, - async destroy() { - const stopping = [...indexers] - const lastIndexer = stopping.pop() - await Promise.all( - stopping.map((indexer) => - indexer.destroy({ skipDb: true, skipRedis: true }), - ), - ) - await lastIndexer?.destroy() - }, - } -} - -export type BskyIndexers = { - db: bsky.Database - list: bsky.BskyIndexer[] - start(): Promise - destroy(): Promise -} - -export async function processAll( - network: TestNetworkNoAppView, - ingester: bsky.BskyIngester, -) { - await network.pds.processAll() - await ingestAll(network, ingester) - // eslint-disable-next-line no-constant-condition - while (true) { - // check indexers - const keys = [...Array(ingester.sub.opts.partitionCount)].map( - (_, i) => `repo:${i}`, - ) - const results = await ingester.sub.ctx.redis.streamLengths(keys) - const indexersCaughtUp = results.every((len) => len === 0) - if (indexersCaughtUp) return - await wait(50) - } -} - -export async function ingestAll( - network: TestNetworkNoAppView, - ingester: bsky.BskyIngester, -) { - const sequencer = network.pds.ctx.sequencer - await network.pds.processAll() - // eslint-disable-next-line no-constant-condition - while (true) { - await wait(50) - // check ingester - const [ingesterCursor, curr] = await Promise.all([ - ingester.sub.getCursor(), - sequencer.curr(), - ]) - const ingesterCaughtUp = curr !== null && ingesterCursor === curr - if (ingesterCaughtUp) return + await this.server.destroy() + await this.bsync.destroy() + await this.dataplane.destroy() + await this.sub.destroy() + await this.db.close() } } diff --git a/packages/dev-env/src/mock/index.ts b/packages/dev-env/src/mock/index.ts index ab818dfc24c..f115a1112ef 100644 --- a/packages/dev-env/src/mock/index.ts +++ b/packages/dev-env/src/mock/index.ts @@ -1,5 +1,6 @@ import { AtUri } from '@atproto/syntax' import AtpAgent from '@atproto/api' +import { Database } from '@atproto/bsky' import { REASONSPAM, REASONOTHER, @@ -186,28 +187,16 @@ export async function generateMockSetup(env: TestNetwork) { }, ) - const ctx = env.bsky.ctx - if (ctx) { - const labelSrvc = ctx.services.label(ctx.db.getPrimary()) - await labelSrvc.createLabels([ - { - src: env.ozone.ctx.cfg.service.did, - uri: labeledPost.uri, - cid: labeledPost.cid, - val: 'nudity', - neg: false, - cts: new Date().toISOString(), - }, - { - src: env.ozone.ctx.cfg.service.did, - uri: filteredPost.uri, - cid: filteredPost.cid, - val: 'dmca-violation', - neg: false, - cts: new Date().toISOString(), - }, - ]) - } + await createLabel(env.bsky.db, { + uri: labeledPost.uri, + cid: labeledPost.cid, + val: 'nudity', + }) + await createLabel(env.bsky.db, { + uri: filteredPost.uri, + cid: filteredPost.cid, + val: 'dmca-violation', + }) // a set of replies for (let i = 0; i < 100; i++) { @@ -341,3 +330,20 @@ export async function generateMockSetup(env: TestNetwork) { function ucfirst(str: string): string { return str.at(0)?.toUpperCase() + str.slice(1) } + +const createLabel = async ( + db: Database, + opts: { uri: string; cid: string; val: string }, +) => { + await db.db + .insertInto('label') + .values({ + uri: opts.uri, + cid: opts.cid, + val: opts.val, + cts: new Date().toISOString(), + neg: false, + src: 'did:example:labeler', + }) + .execute() +} diff --git a/packages/dev-env/src/network-no-appview.ts b/packages/dev-env/src/network-no-appview.ts index 44701ece35e..3272245280c 100644 --- a/packages/dev-env/src/network-no-appview.ts +++ b/packages/dev-env/src/network-no-appview.ts @@ -32,7 +32,7 @@ export class TestNetworkNoAppView { return fg } - getSeedClient(): SeedClient { + getSeedClient(): SeedClient { const agent = this.pds.getClient() return new SeedClient(this, agent) } diff --git a/packages/dev-env/src/network.ts b/packages/dev-env/src/network.ts index 4e29c3c9384..c90e2c181f5 100644 --- a/packages/dev-env/src/network.ts +++ b/packages/dev-env/src/network.ts @@ -56,16 +56,11 @@ export class TestNetwork extends TestNetworkNoAppView { plcUrl: plc.url, pdsPort, repoProvider: `ws://localhost:${pdsPort}`, - labelProvider: `http://localhost:${ozonePort}`, dbPostgresSchema: `appview_${dbPostgresSchema}`, - dbPrimaryPostgresUrl: dbPostgresUrl, + dbPostgresUrl, redisHost, modServiceDid: ozoneDid, ...params.bsky, - indexer: { - ...params.bsky?.indexer, - moderationPushUrl: `http://admin:${ADMIN_PASSWORD}@localhost:${ozonePort}`, - }, }) const pds = await TestPds.create({ @@ -98,13 +93,12 @@ export class TestNetwork extends TestNetworkNoAppView { } async processFullSubscription(timeout = 5000) { - const sub = this.bsky.indexer.sub + const sub = this.bsky.sub const start = Date.now() const lastSeq = await this.pds.ctx.sequencer.curr() if (!lastSeq) return while (Date.now() - start < timeout) { - const partitionState = sub.partitions.get(0) - if (partitionState?.cursor >= lastSeq) { + if (sub.seenSeq !== null && sub.seenSeq >= lastSeq) { // has seen last seq, just need to wait for it to finish processing await sub.repoQueue.main.onIdle() return @@ -117,9 +111,7 @@ export class TestNetwork extends TestNetworkNoAppView { async processAll(timeout?: number) { await this.pds.processAll() await this.processFullSubscription(timeout) - await this.bsky.processAll() - await this.ozone.processAll() - await this.bsky.ingester.ctx.labelSubscription?.fetchLabels() + await this.bsky.sub.background.processAll() } async serviceHeaders(did: string, aud?: string) { diff --git a/packages/dev-env/src/seed/basic.ts b/packages/dev-env/src/seed/basic.ts index 47c299dce45..45583813afb 100644 --- a/packages/dev-env/src/seed/basic.ts +++ b/packages/dev-env/src/seed/basic.ts @@ -1,7 +1,13 @@ -import { SeedClient } from './client' import usersSeed from './users' +import { TestBsky } from '../bsky' +import { TestNetwork } from '../network' +import { TestNetworkNoAppView } from '../network-no-appview' +import { SeedClient } from './client' -export default async (sc: SeedClient, users = true) => { +export default async ( + sc: SeedClient, + users = true, +) => { if (users) await usersSeed(sc) const alice = sc.dids.alice @@ -129,6 +135,25 @@ export default async (sc: SeedClient, users = true) => { await sc.repost(dan, sc.posts[alice][1].ref) await sc.repost(dan, alicesReplyToBob.ref) + if (sc.network instanceof TestNetwork) { + const bsky = sc.network.bsky + await createLabel(bsky, { + val: 'test-label', + uri: sc.posts[alice][2].ref.uriStr, + cid: sc.posts[alice][2].ref.cidStr, + }) + await createLabel(bsky, { + val: 'test-label', + uri: sc.replies[bob][0].ref.uriStr, + cid: sc.replies[bob][0].ref.cidStr, + }) + await createLabel(bsky, { + val: 'test-label-2', + uri: sc.replies[bob][0].ref.uriStr, + cid: sc.replies[bob][0].ref.cidStr, + }) + } + return sc } @@ -144,3 +169,20 @@ export const replies = { bob: ['hear that label_me label_me_2'], carol: ['of course'], } + +const createLabel = async ( + bsky: TestBsky, + opts: { uri: string; cid: string; val: string }, +) => { + await bsky.db.db + .insertInto('label') + .values({ + uri: opts.uri, + cid: opts.cid, + val: opts.val, + cts: new Date().toISOString(), + neg: false, + src: 'did:example:labeler', // this did is also configured on labelsFromIssuerDids + }) + .execute() +} diff --git a/packages/dev-env/src/seed/client.ts b/packages/dev-env/src/seed/client.ts index 5b7a614228f..984115731dd 100644 --- a/packages/dev-env/src/seed/client.ts +++ b/packages/dev-env/src/seed/client.ts @@ -46,7 +46,9 @@ export class RecordRef { } } -export class SeedClient { +export class SeedClient< + Network extends TestNetworkNoAppView = TestNetworkNoAppView, +> { accounts: Record< string, { @@ -82,7 +84,7 @@ export class SeedClient { > dids: Record - constructor(public network: TestNetworkNoAppView, public agent: AtpAgent) { + constructor(public network: Network, public agent: AtpAgent) { this.accounts = {} this.profiles = {} this.follows = {} diff --git a/packages/dev-env/src/types.ts b/packages/dev-env/src/types.ts index db4a7331ae0..dbedfaced4f 100644 --- a/packages/dev-env/src/types.ts +++ b/packages/dev-env/src/types.ts @@ -2,7 +2,6 @@ import * as pds from '@atproto/pds' import * as bsky from '@atproto/bsky' import * as bsync from '@atproto/bsync' import * as ozone from '@atproto/ozone' -import { ImageInvalidator } from '@atproto/bsky' import { ExportableKeypair } from '@atproto/crypto' export type PlcConfig = { @@ -18,14 +17,11 @@ export type PdsConfig = Partial & { export type BskyConfig = Partial & { plcUrl: string repoProvider: string - labelProvider: string - dbPrimaryPostgresUrl: string + dbPostgresUrl: string + dbPostgresSchema: string redisHost: string pdsPort: number - imgInvalidator?: ImageInvalidator migration?: string - indexer?: Partial - ingester?: Partial } export type BsyncConfig = Partial & { diff --git a/packages/dev-env/src/util.ts b/packages/dev-env/src/util.ts index 7d6091023f6..679ca89c7a8 100644 --- a/packages/dev-env/src/util.ts +++ b/packages/dev-env/src/util.ts @@ -7,7 +7,7 @@ export const mockNetworkUtilities = (pds: TestPds, bsky?: TestBsky) => { mockResolvers(pds.ctx.idResolver, pds) if (bsky) { mockResolvers(bsky.ctx.idResolver, pds) - mockResolvers(bsky.indexer.ctx.idResolver, pds) + mockResolvers(bsky.dataplane.idResolver, pds) } } diff --git a/packages/identity/src/did/atproto-data.ts b/packages/identity/src/did/atproto-data.ts index c03f76ef598..c0cd9829739 100644 --- a/packages/identity/src/did/atproto-data.ts +++ b/packages/identity/src/did/atproto-data.ts @@ -20,7 +20,13 @@ export { export const getKey = (doc: DidDocument): string | undefined => { const key = getSigningKey(doc) if (!key) return undefined + return getDidKeyFromMultibase(key) +} +export const getDidKeyFromMultibase = (key: { + type: string + publicKeyMultibase: string +}): string | undefined => { const keyBytes = crypto.multibaseToBytes(key.publicKeyMultibase) let didKey: string | undefined = undefined if (key.type === 'EcdsaSecp256r1VerificationKey2019') { diff --git a/packages/ozone/tests/__snapshots__/moderation-events.test.ts.snap b/packages/ozone/tests/__snapshots__/moderation-events.test.ts.snap index ac48d862f58..fcb73413622 100644 --- a/packages/ozone/tests/__snapshots__/moderation-events.test.ts.snap +++ b/packages/ozone/tests/__snapshots__/moderation-events.test.ts.snap @@ -5,85 +5,63 @@ Object { "createdAt": "1970-01-01T00:00:00.000Z", "createdBy": "user(2)", "event": Object { - "$type": "com.atproto.admin.defs#modEventLabel", - "comment": "[AutoModerator]: Applying labels", - "createLabelVals": Array [ - "test-label", - ], - "negateLabelVals": Array [], + "$type": "com.atproto.admin.defs#modEventReport", + "comment": "X", + "reportType": "com.atproto.moderation.defs#reasonMisleading", }, "id": 1, "subject": Object { - "$type": "com.atproto.admin.defs#recordView", - "blobCids": Array [], - "cid": "cids(0)", + "$type": "com.atproto.admin.defs#repoView", + "did": "user(0)", + "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", - "moderation": Object {}, - "repo": Object { - "did": "user(0)", - "handle": "alice.test", - "indexedAt": "1970-01-01T00:00:00.000Z", - "moderation": Object { - "subjectStatus": Object { - "createdAt": "1970-01-01T00:00:00.000Z", - "id": 1, - "lastReportedAt": "1970-01-01T00:00:00.000Z", - "lastReviewedAt": "1970-01-01T00:00:00.000Z", - "lastReviewedBy": "user(1)", - "reviewState": "com.atproto.admin.defs#reviewEscalated", - "subject": Object { - "$type": "com.atproto.admin.defs#repoRef", - "did": "user(0)", - }, - "subjectBlobCids": Array [], - "subjectRepoHandle": "alice.test", - "tags": Array [ - "lang:und", - ], - "takendown": false, - "updatedAt": "1970-01-01T00:00:00.000Z", + "moderation": Object { + "subjectStatus": Object { + "createdAt": "1970-01-01T00:00:00.000Z", + "id": 1, + "lastReportedAt": "1970-01-01T00:00:00.000Z", + "lastReviewedAt": "1970-01-01T00:00:00.000Z", + "lastReviewedBy": "user(1)", + "reviewState": "com.atproto.admin.defs#reviewEscalated", + "subject": Object { + "$type": "com.atproto.admin.defs#repoRef", + "did": "user(0)", }, + "subjectBlobCids": Array [], + "subjectRepoHandle": "alice.test", + "tags": Array [ + "lang:und", + ], + "takendown": false, + "updatedAt": "1970-01-01T00:00:00.000Z", }, - "relatedRecords": Array [ - Object { - "$type": "app.bsky.actor.profile", - "avatar": Object { - "$type": "blob", - "mimeType": "image/jpeg", - "ref": Object { - "$link": "cids(2)", - }, - "size": 3976, - }, - "description": "its me!", - "displayName": "ali", - "labels": Object { - "$type": "com.atproto.label.defs#selfLabels", - "values": Array [ - Object { - "val": "self-label-a", - }, - Object { - "val": "self-label-b", - }, - ], + }, + "relatedRecords": Array [ + Object { + "$type": "app.bsky.actor.profile", + "avatar": Object { + "$type": "blob", + "mimeType": "image/jpeg", + "ref": Object { + "$link": "cids(0)", }, + "size": 3976, }, - ], - }, - "uri": "record(0)", - "value": Object { - "$type": "app.bsky.feed.post", - "createdAt": "1970-01-01T00:00:00.000Z", - "embed": Object { - "$type": "app.bsky.embed.record", - "record": Object { - "cid": "cids(1)", - "uri": "record(1)", + "description": "its me!", + "displayName": "ali", + "labels": Object { + "$type": "com.atproto.label.defs#selfLabels", + "values": Array [ + Object { + "val": "self-label-a", + }, + Object { + "val": "self-label-b", + }, + ], }, }, - "text": "yoohoo label_me", - }, + ], }, "subjectBlobCids": Array [], "subjectBlobs": Array [], @@ -101,7 +79,7 @@ Array [ "comment": "X", "reportType": "com.atproto.moderation.defs#reasonSpam", }, - "id": 13, + "id": 11, "subject": Object { "$type": "com.atproto.admin.defs#repoRef", "did": "user(0)", @@ -120,7 +98,7 @@ Array [ ], "remove": Array [], }, - "id": 8, + "id": 6, "subject": Object { "$type": "com.atproto.admin.defs#repoRef", "did": "user(0)", @@ -137,7 +115,7 @@ Array [ "comment": "X", "reportType": "com.atproto.moderation.defs#reasonSpam", }, - "id": 7, + "id": 5, "subject": Object { "$type": "com.atproto.admin.defs#repoRef", "did": "user(0)", @@ -159,7 +137,7 @@ Array [ "comment": "X", "reportType": "com.atproto.moderation.defs#reasonSpam", }, - "id": 12, + "id": 10, "subject": Object { "$type": "com.atproto.repo.strongRef", "cid": "cids(0)", @@ -178,7 +156,7 @@ Array [ ], "remove": Array [], }, - "id": 6, + "id": 4, "subject": Object { "$type": "com.atproto.repo.strongRef", "cid": "cids(0)", @@ -196,7 +174,7 @@ Array [ "comment": "X", "reportType": "com.atproto.moderation.defs#reasonSpam", }, - "id": 5, + "id": 3, "subject": Object { "$type": "com.atproto.repo.strongRef", "cid": "cids(0)", diff --git a/packages/ozone/tests/__snapshots__/moderation.test.ts.snap b/packages/ozone/tests/__snapshots__/moderation.test.ts.snap index 1cd4c192081..39d2b6d4890 100644 --- a/packages/ozone/tests/__snapshots__/moderation.test.ts.snap +++ b/packages/ozone/tests/__snapshots__/moderation.test.ts.snap @@ -4,7 +4,7 @@ exports[`moderation reporting creates reports of a record. 1`] = ` Array [ Object { "createdAt": "1970-01-01T00:00:00.000Z", - "id": 7, + "id": 5, "reasonType": "com.atproto.moderation.defs#reasonSpam", "reportedBy": "user(0)", "subject": Object { @@ -15,7 +15,7 @@ Array [ }, Object { "createdAt": "1970-01-01T00:00:00.000Z", - "id": 9, + "id": 7, "reason": "defamation", "reasonType": "com.atproto.moderation.defs#reasonOther", "reportedBy": "user(1)", @@ -32,7 +32,7 @@ exports[`moderation reporting creates reports of a repo. 1`] = ` Array [ Object { "createdAt": "1970-01-01T00:00:00.000Z", - "id": 3, + "id": 1, "reasonType": "com.atproto.moderation.defs#reasonSpam", "reportedBy": "user(0)", "subject": Object { @@ -42,7 +42,7 @@ Array [ }, Object { "createdAt": "1970-01-01T00:00:00.000Z", - "id": 5, + "id": 3, "reason": "impersonation", "reasonType": "com.atproto.moderation.defs#reasonOther", "reportedBy": "user(2)", diff --git a/packages/ozone/tests/moderation-events.test.ts b/packages/ozone/tests/moderation-events.test.ts index 12277ea77a4..fbe571a8172 100644 --- a/packages/ozone/tests/moderation-events.test.ts +++ b/packages/ozone/tests/moderation-events.test.ts @@ -22,13 +22,13 @@ describe('moderation-events', () => { ) => { return pdsAgent.api.com.atproto.admin.emitModerationEvent(eventData, { encoding: 'application/json', - headers: network.bsky.adminAuthHeaders('moderator'), + headers: network.ozone.adminAuthHeaders('moderator'), }) } const queryModerationEvents = (eventQuery) => agent.api.com.atproto.admin.queryModerationEvents(eventQuery, { - headers: network.bsky.adminAuthHeaders('moderator'), + headers: network.ozone.adminAuthHeaders('moderator'), }) const seedEvents = async () => { @@ -203,11 +203,11 @@ describe('moderation-events', () => { const defaultEvents = await getPaginatedEvents() const reversedEvents = await getPaginatedEvents('asc') - expect(allEvents.data.events.length).toEqual(7) + expect(allEvents.data.events.length).toEqual(6) expect(defaultEvents.length).toEqual(allEvents.data.events.length) expect(reversedEvents.length).toEqual(allEvents.data.events.length) // First event in the reversed list is the last item in the default list - expect(reversedEvents[0].id).toEqual(defaultEvents[6].id) + expect(reversedEvents[0].id).toEqual(defaultEvents[5].id) }) it('returns report events matching reportType filters', async () => { @@ -240,7 +240,7 @@ describe('moderation-events', () => { expect(eventsWithX.data.events.length).toEqual(10) expect(eventsWithTest.data.events.length).toEqual(0) - expect(eventsWithComment.data.events.length).toEqual(12) + expect(eventsWithComment.data.events.length).toEqual(10) }) it('returns events matching filter params for labels', async () => { @@ -325,7 +325,7 @@ describe('moderation-events', () => { }) const addEvent = await tagEvent({ add: ['L1', 'L2'], remove: [] }) const addAndRemoveEvent = await tagEvent({ add: ['L3'], remove: ['L2'] }) - const [addFinder, addAndRemoveFinder, removeFinder] = await Promise.all([ + const [addFinder, addAndRemoveFinder, _removeFinder] = await Promise.all([ queryModerationEvents({ addedTags: ['L1'], }), @@ -356,7 +356,7 @@ describe('moderation-events', () => { it('gets an event by specific id', async () => { const { data } = await pdsAgent.api.com.atproto.admin.getModerationEvent( { id: 1 }, - { headers: network.bsky.adminAuthHeaders('moderator') }, + { headers: network.ozone.adminAuthHeaders('moderator') }, ) expect(forSnapshot(data)).toMatchSnapshot() }) diff --git a/packages/ozone/tests/moderation-statuses.test.ts b/packages/ozone/tests/moderation-statuses.test.ts index 14184454e62..527611d5313 100644 --- a/packages/ozone/tests/moderation-statuses.test.ts +++ b/packages/ozone/tests/moderation-statuses.test.ts @@ -19,13 +19,13 @@ describe('moderation-statuses', () => { const emitModerationEvent = async (eventData) => { return pdsAgent.api.com.atproto.admin.emitModerationEvent(eventData, { encoding: 'application/json', - headers: network.bsky.adminAuthHeaders('moderator'), + headers: network.ozone.adminAuthHeaders('moderator'), }) } const queryModerationStatuses = (statusQuery) => agent.api.com.atproto.admin.queryModerationStatuses(statusQuery, { - headers: network.bsky.adminAuthHeaders('moderator'), + headers: network.ozone.adminAuthHeaders('moderator'), }) const seedEvents = async () => { diff --git a/packages/ozone/tests/moderation.test.ts b/packages/ozone/tests/moderation.test.ts index b9899bdcdab..79aae3938c9 100644 --- a/packages/ozone/tests/moderation.test.ts +++ b/packages/ozone/tests/moderation.test.ts @@ -607,7 +607,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.bsky.adminAuthHeaders('triage'), + headers: network.ozone.adminAuthHeaders('triage'), }, ) await expect(attemptLabel).rejects.toThrow( @@ -748,7 +748,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.bsky.adminAuthHeaders('moderator'), + headers: network.ozone.adminAuthHeaders('moderator'), }, ) // cleanup @@ -775,7 +775,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.bsky.adminAuthHeaders('triage'), + headers: network.ozone.adminAuthHeaders('triage'), }, ) await expect(attemptTakedownTriage).rejects.toThrow( @@ -800,7 +800,7 @@ describe('moderation', () => { const { data: statusesAfterTakedown } = await agent.api.com.atproto.admin.queryModerationStatuses( { subject: sc.dids.bob }, - { headers: network.bsky.adminAuthHeaders('moderator') }, + { headers: network.ozone.adminAuthHeaders('moderator') }, ) expect(statusesAfterTakedown.subjectStatuses[0]).toMatchObject({ @@ -818,11 +818,11 @@ describe('moderation', () => { const [{ data: eventList }, { data: statuses }] = await Promise.all([ agent.api.com.atproto.admin.queryModerationEvents( { subject: sc.dids.bob }, - { headers: network.bsky.adminAuthHeaders('moderator') }, + { headers: network.ozone.adminAuthHeaders('moderator') }, ), agent.api.com.atproto.admin.queryModerationStatuses( { subject: sc.dids.bob }, - { headers: network.bsky.adminAuthHeaders('moderator') }, + { headers: network.ozone.adminAuthHeaders('moderator') }, ), ]) @@ -879,7 +879,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.bsky.adminAuthHeaders(), + headers: network.ozone.adminAuthHeaders(), }, ) return result.data @@ -901,7 +901,7 @@ describe('moderation', () => { }, { encoding: 'application/json', - headers: network.bsky.adminAuthHeaders(), + headers: network.ozone.adminAuthHeaders(), }, ) } @@ -909,7 +909,7 @@ describe('moderation', () => { async function getRecordLabels(uri: string) { const result = await agent.api.com.atproto.admin.getRecord( { uri }, - { headers: network.bsky.adminAuthHeaders() }, + { headers: network.ozone.adminAuthHeaders() }, ) const labels = result.data.labels ?? [] return labels.map((l) => l.val) @@ -918,7 +918,7 @@ describe('moderation', () => { async function getRepoLabels(did: string) { const result = await agent.api.com.atproto.admin.getRepo( { did }, - { headers: network.bsky.adminAuthHeaders() }, + { headers: network.ozone.adminAuthHeaders() }, ) const labels = result.data.labels ?? [] return labels.map((l) => l.val) @@ -933,7 +933,7 @@ describe('moderation', () => { const { ctx } = network.bsky post = sc.posts[sc.dids.carol][0] blob = post.images[1] - imageUri = ctx.imgUriBuilder + imageUri = ctx.views.imgUriBuilder .getPresetUri( 'feed_thumbnail', sc.dids.carol, @@ -974,7 +974,8 @@ describe('moderation', () => { }) }) - it('prevents image blob from being served, even when cached.', async () => { + // @TODO add back in with image invalidation, see bluesky-social/atproto#2087 + it.skip('prevents image blob from being served, even when cached.', async () => { const fetchImage = await fetch(imageUri) expect(fetchImage.status).toEqual(404) expect(await fetchImage.json()).toEqual({ message: 'Image not found' }) diff --git a/packages/ozone/tests/repo-search.test.ts b/packages/ozone/tests/repo-search.test.ts index 0d41b014c1b..1704e934206 100644 --- a/packages/ozone/tests/repo-search.test.ts +++ b/packages/ozone/tests/repo-search.test.ts @@ -117,7 +117,6 @@ describe('admin repo search view', () => { { headers }, ) - expect(full.data.repos.length).toEqual(15) expect(results(paginatedAll)).toEqual(results([full.data])) }) }) diff --git a/packages/pds/tests/_util.ts b/packages/pds/tests/_util.ts index 5624ac9a65a..ee57c62b9fe 100644 --- a/packages/pds/tests/_util.ts +++ b/packages/pds/tests/_util.ts @@ -43,11 +43,11 @@ export const forSnapshot = (obj: unknown) => { return constantDate } } - if (str.match(/^\d+::bafy/)) { + // handles both pds and appview cursor separators + if (str.match(/^\d+(?:__|::)bafy/)) { return constantKeysetCursor } - - if (str.match(/^\d+::did:plc/)) { + if (str.match(/^\d+(?:__|::)did:plc/)) { return constantDidCursor } if (str.match(/\/image\/[^/]+\/.+\/did:plc:[^/]+\/[^/]+@[\w]+$/)) { diff --git a/packages/pds/tests/proxied/__snapshots__/feedgen.test.ts.snap b/packages/pds/tests/proxied/__snapshots__/feedgen.test.ts.snap index 1d68928d7e0..e4c09b14c51 100644 --- a/packages/pds/tests/proxied/__snapshots__/feedgen.test.ts.snap +++ b/packages/pds/tests/proxied/__snapshots__/feedgen.test.ts.snap @@ -86,13 +86,13 @@ Object { "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(4)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(4)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(4)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(4)@jpeg", }, Object { "alt": "../dev-env/src/seed/img/key-alt.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(3)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(3)/cids(5)@jpeg", }, ], }, @@ -100,8 +100,8 @@ Object { "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", - "did": "user(3)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(1)@jpeg", + "did": "user(4)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], diff --git a/packages/pds/tests/proxied/__snapshots__/views.test.ts.snap b/packages/pds/tests/proxied/__snapshots__/views.test.ts.snap index 73781d5435a..22d39d28164 100644 --- a/packages/pds/tests/proxied/__snapshots__/views.test.ts.snap +++ b/packages/pds/tests/proxied/__snapshots__/views.test.ts.snap @@ -103,7 +103,7 @@ Object { Object { "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "user(2)", "val": "repo-action-label", }, @@ -114,7 +114,7 @@ Object { }, }, ], - "cursor": "1:3", + "cursor": "1:2:3", } `; @@ -183,7 +183,7 @@ Array [ Object { "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(6)", + "src": "did:example:labeler", "uri": "user(5)", "val": "repo-action-label", }, @@ -258,7 +258,7 @@ Array [ Object { "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(6)", + "src": "did:example:labeler", "uri": "user(5)", "val": "repo-action-label", }, @@ -303,24 +303,7 @@ Object { ], }, "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [ - Object { - "cid": "cids(0)", - "cts": "1970-01-01T00:00:00.000Z", - "neg": false, - "src": "user(2)", - "uri": "record(0)", - "val": "test-label", - }, - Object { - "cid": "cids(0)", - "cts": "1970-01-01T00:00:00.000Z", - "neg": false, - "src": "user(2)", - "uri": "record(0)", - "val": "test-label-2", - }, - ], + "labels": Array [], "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", @@ -362,8 +345,8 @@ Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", - "did": "user(3)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", + "did": "user(2)", "displayName": "ali", "handle": "alice.test", "labels": Array [ @@ -371,7 +354,7 @@ Object { "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "user(2)", "uri": "record(4)", "val": "self-label-a", }, @@ -379,7 +362,7 @@ Object { "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "user(2)", "uri": "record(4)", "val": "self-label-b", }, @@ -406,8 +389,8 @@ Object { "root": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", - "did": "user(3)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(1)@jpeg", + "did": "user(2)", "displayName": "ali", "handle": "alice.test", "labels": Array [ @@ -415,7 +398,7 @@ Object { "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "user(2)", "uri": "record(4)", "val": "self-label-a", }, @@ -423,7 +406,7 @@ Object { "cid": "cids(4)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "user(2)", "uri": "record(4)", "val": "self-label-b", }, @@ -525,9 +508,11 @@ Object { "cid": "cids(0)", "creator": Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", + "description": "its me!", "did": "user(1)", "displayName": "ali", "handle": "alice.test", + "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { "cid": "cids(2)", @@ -568,9 +553,11 @@ Object { "cid": "cids(0)", "creator": Object { "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(1)@jpeg", + "description": "its me!", "did": "user(1)", "displayName": "ali", "handle": "alice.test", + "indexedAt": "1970-01-01T00:00:00.000Z", "labels": Array [ Object { "cid": "cids(2)", @@ -753,24 +740,7 @@ Object { ], }, "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [ - Object { - "cid": "cids(4)", - "cts": "1970-01-01T00:00:00.000Z", - "neg": false, - "src": "user(4)", - "uri": "record(3)", - "val": "test-label", - }, - Object { - "cid": "cids(4)", - "cts": "1970-01-01T00:00:00.000Z", - "neg": false, - "src": "user(4)", - "uri": "record(3)", - "val": "test-label-2", - }, - ], + "labels": Array [], "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", @@ -881,24 +851,7 @@ Object { ], }, "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [ - Object { - "cid": "cids(4)", - "cts": "1970-01-01T00:00:00.000Z", - "neg": false, - "src": "user(4)", - "uri": "record(3)", - "val": "test-label", - }, - Object { - "cid": "cids(4)", - "cts": "1970-01-01T00:00:00.000Z", - "neg": false, - "src": "user(4)", - "uri": "record(3)", - "val": "test-label-2", - }, - ], + "labels": Array [], "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", @@ -1063,14 +1016,14 @@ Object { "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(5)", + "did": "user(4)", "handle": "dan.test", "labels": Array [ Object { "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(4)", - "uri": "user(5)", + "src": "did:example:labeler", + "uri": "user(4)", "val": "repo-action-label", }, ], @@ -1087,7 +1040,7 @@ Object { "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(6)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -1177,16 +1130,7 @@ Object { }, }, "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [ - Object { - "cid": "cids(6)", - "cts": "1970-01-01T00:00:00.000Z", - "neg": false, - "src": "user(4)", - "uri": "record(6)", - "val": "test-label", - }, - ], + "labels": Array [], "likeCount": 2, "record": Object { "$type": "app.bsky.feed.post", @@ -1621,7 +1565,7 @@ Object { Object { "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "user(2)", "val": "repo-action-label", }, @@ -1638,8 +1582,8 @@ Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], @@ -1656,30 +1600,13 @@ Object { "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(5)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [ - Object { - "cid": "cids(4)", - "cts": "1970-01-01T00:00:00.000Z", - "neg": false, - "src": "user(3)", - "uri": "record(3)", - "val": "test-label", - }, - Object { - "cid": "cids(4)", - "cts": "1970-01-01T00:00:00.000Z", - "neg": false, - "src": "user(3)", - "uri": "record(3)", - "val": "test-label-2", - }, - ], + "labels": Array [], "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", @@ -1816,7 +1743,7 @@ Object { Object { "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "user(2)", "val": "repo-action-label", }, @@ -1839,7 +1766,7 @@ Object { Object { "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "user(2)", "val": "repo-action-label", }, @@ -1856,7 +1783,7 @@ Object { "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(6)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -1875,13 +1802,13 @@ Object { "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(7)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(7)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(5)@jpeg", }, Object { "alt": "../dev-env/src/seed/img/key-alt.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(7)/cids(8)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(7)/cids(8)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(8)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(8)@jpeg", }, ], }, @@ -1889,8 +1816,8 @@ Object { "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], @@ -2001,7 +1928,7 @@ Object { "reason": Object { "$type": "app.bsky.feed.defs#reasonRepost", "by": Object { - "did": "user(6)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -2072,8 +1999,8 @@ Object { "parent": Object { "$type": "app.bsky.feed.defs#postView", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], @@ -2090,30 +2017,13 @@ Object { "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(5)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [ - Object { - "cid": "cids(4)", - "cts": "1970-01-01T00:00:00.000Z", - "neg": false, - "src": "user(3)", - "uri": "record(3)", - "val": "test-label", - }, - Object { - "cid": "cids(4)", - "cts": "1970-01-01T00:00:00.000Z", - "neg": false, - "src": "user(3)", - "uri": "record(3)", - "val": "test-label-2", - }, - ], + "labels": Array [], "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", @@ -2200,7 +2110,7 @@ Object { Object { "post": Object { "author": Object { - "did": "user(6)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -2328,8 +2238,8 @@ Object { Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], @@ -2346,30 +2256,13 @@ Object { "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(5)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(5)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(4)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(4)/cids(5)@jpeg", }, ], }, "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [ - Object { - "cid": "cids(4)", - "cts": "1970-01-01T00:00:00.000Z", - "neg": false, - "src": "user(3)", - "uri": "record(3)", - "val": "test-label", - }, - Object { - "cid": "cids(4)", - "cts": "1970-01-01T00:00:00.000Z", - "neg": false, - "src": "user(3)", - "uri": "record(3)", - "val": "test-label-2", - }, - ], + "labels": Array [], "likeCount": 0, "record": Object { "$type": "app.bsky.feed.post", @@ -2540,7 +2433,7 @@ Object { Object { "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "user(2)", "val": "repo-action-label", }, @@ -2558,7 +2451,7 @@ Object { "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(6)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -2648,16 +2541,7 @@ Object { }, }, "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [ - Object { - "cid": "cids(11)", - "cts": "1970-01-01T00:00:00.000Z", - "neg": false, - "src": "user(3)", - "uri": "record(13)", - "val": "test-label", - }, - ], + "labels": Array [], "likeCount": 2, "record": Object { "$type": "app.bsky.feed.post", @@ -2680,8 +2564,8 @@ Object { Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], @@ -2761,7 +2645,7 @@ Object { Object { "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "user(2)", "val": "repo-action-label", }, @@ -2778,7 +2662,7 @@ Object { "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "did": "user(6)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -2797,13 +2681,13 @@ Object { "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(7)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(7)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(5)@jpeg", }, Object { "alt": "../dev-env/src/seed/img/key-alt.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(7)/cids(8)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(7)/cids(8)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(8)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(8)@jpeg", }, ], }, @@ -2811,8 +2695,8 @@ Object { "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], @@ -2930,7 +2814,7 @@ Object { Object { "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(3)", + "src": "did:example:labeler", "uri": "user(2)", "val": "repo-action-label", }, @@ -2959,7 +2843,7 @@ Object { Object { "post": Object { "author": Object { - "did": "user(6)", + "did": "user(5)", "handle": "carol.test", "labels": Array [], "viewer": Object { @@ -2977,13 +2861,13 @@ Object { "images": Array [ Object { "alt": "../dev-env/src/seed/img/key-landscape-small.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(7)/cids(5)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(7)/cids(5)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(5)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(5)@jpeg", }, Object { "alt": "../dev-env/src/seed/img/key-alt.jpg", - "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(7)/cids(8)@jpeg", - "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(7)/cids(8)@jpeg", + "fullsize": "https://bsky.public.url/img/feed_fullsize/plain/user(6)/cids(8)@jpeg", + "thumb": "https://bsky.public.url/img/feed_thumbnail/plain/user(6)/cids(8)@jpeg", }, ], }, @@ -2991,8 +2875,8 @@ Object { "record": Object { "$type": "app.bsky.embed.record#viewRecord", "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], @@ -3075,8 +2959,8 @@ Object { Object { "post": Object { "author": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(1)@jpeg", - "did": "user(4)", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(1)@jpeg", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "labels": Array [], @@ -3215,7 +3099,7 @@ Object { Object { "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(1)", + "src": "did:example:labeler", "uri": "user(0)", "val": "repo-action-label", }, @@ -3227,9 +3111,9 @@ Object { }, }, Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(3)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(2)/cids(0)@jpeg", "description": "its me!", - "did": "user(2)", + "did": "user(1)", "displayName": "ali", "handle": "alice.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -3238,7 +3122,7 @@ Object { "cid": "cids(1)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(2)", + "src": "user(1)", "uri": "record(1)", "val": "self-label-a", }, @@ -3246,7 +3130,7 @@ Object { "cid": "cids(1)", "cts": "1970-01-01T00:00:00.000Z", "neg": false, - "src": "user(2)", + "src": "user(1)", "uri": "record(1)", "val": "self-label-b", }, @@ -3258,9 +3142,9 @@ Object { }, ], "subject": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(5)/cids(0)@jpeg", + "avatar": "https://bsky.public.url/img/avatar/plain/user(4)/cids(0)@jpeg", "description": "hi im bob label_me", - "did": "user(4)", + "did": "user(3)", "displayName": "bobby", "handle": "bob.test", "indexedAt": "1970-01-01T00:00:00.000Z", @@ -3515,48 +3399,6 @@ Object { exports[`proxies view requests unspecced.getPopularFeedGenerators 1`] = ` Object { - "cursor": "0000000000000::bafycid", - "feeds": Array [ - Object { - "cid": "cids(0)", - "creator": Object { - "avatar": "https://bsky.public.url/img/avatar/plain/user(1)/cids(1)@jpeg", - "description": "its me!", - "did": "user(0)", - "displayName": "ali", - "handle": "alice.test", - "indexedAt": "1970-01-01T00:00:00.000Z", - "labels": Array [ - Object { - "cid": "cids(2)", - "cts": "1970-01-01T00:00:00.000Z", - "neg": false, - "src": "user(0)", - "uri": "record(1)", - "val": "self-label-a", - }, - Object { - "cid": "cids(2)", - "cts": "1970-01-01T00:00:00.000Z", - "neg": false, - "src": "user(0)", - "uri": "record(1)", - "val": "self-label-b", - }, - ], - "viewer": Object { - "blockedBy": false, - "muted": false, - }, - }, - "description": "Provides all feed candidates", - "did": "did:example:feedgen", - "displayName": "All", - "indexedAt": "1970-01-01T00:00:00.000Z", - "likeCount": 0, - "uri": "record(0)", - "viewer": Object {}, - }, - ], + "feeds": Array [], } `; diff --git a/packages/pds/tests/proxied/admin.test.ts b/packages/pds/tests/proxied/admin.test.ts index 9906e4d129a..3637b22878c 100644 --- a/packages/pds/tests/proxied/admin.test.ts +++ b/packages/pds/tests/proxied/admin.test.ts @@ -8,7 +8,8 @@ import { import { forSnapshot } from '../_util' import { NotFoundError } from '@atproto/api/src/client/types/app/bsky/feed/getPostThread' -describe('proxies admin requests', () => { +// @TODO skipping during appview v2 buildout, as appview frontends no longer contains moderation endpoints +describe.skip('proxies admin requests', () => { let network: TestNetwork let agent: AtpAgent let sc: SeedClient @@ -32,7 +33,7 @@ describe('proxies admin requests', () => { ) await basicSeed(sc, { inviteCode: invite.code, - addModLabels: true, + addModLabels: network.bsky, }) await network.processAll() }) diff --git a/packages/pds/tests/proxied/feedgen.test.ts b/packages/pds/tests/proxied/feedgen.test.ts index 53e50581b3f..c3cd5d208d6 100644 --- a/packages/pds/tests/proxied/feedgen.test.ts +++ b/packages/pds/tests/proxied/feedgen.test.ts @@ -8,7 +8,6 @@ describe('feedgen proxy view', () => { let network: TestNetwork let agent: AtpAgent let sc: SeedClient - let feedUri: AtUri beforeAll(async () => { @@ -17,7 +16,7 @@ describe('feedgen proxy view', () => { }) agent = network.pds.getClient() sc = network.getSeedClient() - await basicSeed(sc, { addModLabels: true }) + await basicSeed(sc, { addModLabels: network.bsky }) feedUri = AtUri.make(sc.dids.alice, 'app.bsky.feed.generator', 'mutuals') diff --git a/packages/pds/tests/proxied/procedures.test.ts b/packages/pds/tests/proxied/procedures.test.ts index 8c246e38da7..8b488621c41 100644 --- a/packages/pds/tests/proxied/procedures.test.ts +++ b/packages/pds/tests/proxied/procedures.test.ts @@ -17,7 +17,7 @@ describe('proxies appview procedures', () => { }) agent = network.pds.getClient() sc = network.getSeedClient() - await basicSeed(sc, { addModLabels: true }) + await basicSeed(sc, { addModLabels: network.bsky }) await network.processAll() alice = sc.dids.alice bob = sc.dids.bob @@ -142,7 +142,11 @@ describe('proxies appview procedures', () => { { headers: sc.getHeaders(alice) }, ) expect(result1.notifications.length).toBeGreaterThanOrEqual(5) - expect(result1.notifications.every((n) => !n.isRead)).toBe(true) + expect( + result1.notifications.every((n, i) => { + return (i === 0 && !n.isRead) || (i !== 0 && n.isRead) + }), + ).toBe(true) // update last seen const { indexedAt: lastSeenAt } = result1.notifications[2] await agent.api.app.bsky.notification.updateSeen( @@ -163,7 +167,7 @@ describe('proxies appview procedures', () => { expect(result2.notifications).toEqual( result1.notifications.map((n) => ({ ...n, - isRead: n.indexedAt <= lastSeenAt, + isRead: n.indexedAt < lastSeenAt, })), ) }) diff --git a/packages/pds/tests/proxied/read-after-write.test.ts b/packages/pds/tests/proxied/read-after-write.test.ts index 1bd6a463c34..5f70416751a 100644 --- a/packages/pds/tests/proxied/read-after-write.test.ts +++ b/packages/pds/tests/proxied/read-after-write.test.ts @@ -22,7 +22,7 @@ describe('proxy read after write', () => { }) agent = network.pds.getClient() sc = network.getSeedClient() - await basicSeed(sc, { addModLabels: true }) + await basicSeed(sc, { addModLabels: network.bsky }) await network.processAll() alice = sc.dids.alice carol = sc.dids.carol diff --git a/packages/pds/tests/proxied/views.test.ts b/packages/pds/tests/proxied/views.test.ts index 94b76719d70..9cfb7063bdf 100644 --- a/packages/pds/tests/proxied/views.test.ts +++ b/packages/pds/tests/proxied/views.test.ts @@ -19,7 +19,7 @@ describe('proxies view requests', () => { }) agent = network.pds.getClient() sc = network.getSeedClient() - await basicSeed(sc, { addModLabels: true }) + await basicSeed(sc, { addModLabels: network.bsky }) alice = sc.dids.alice bob = sc.dids.bob carol = sc.dids.carol @@ -79,9 +79,8 @@ describe('proxies view requests', () => { { did: sc.dids.carol, order: 2 }, { did: sc.dids.dan, order: 3 }, ] - await network.bsky.ctx.db - .getPrimary() - .db.insertInto('suggested_follow') + await network.bsky.db.db + .insertInto('suggested_follow') .values(suggestions) .execute() @@ -334,7 +333,8 @@ describe('proxies view requests', () => { expect([...pt1.data.feed, ...pt2.data.feed]).toEqual(res.data.feed) }) - it('unspecced.getPopularFeedGenerators', async () => { + // @TODO disabled during appview v2 buildout + it.skip('unspecced.getPopularFeedGenerators', async () => { const res = await agent.api.app.bsky.unspecced.getPopularFeedGenerators( {}, { diff --git a/packages/pds/tests/seeds/basic.ts b/packages/pds/tests/seeds/basic.ts index 1590fda3d05..c0dbf009213 100644 --- a/packages/pds/tests/seeds/basic.ts +++ b/packages/pds/tests/seeds/basic.ts @@ -1,10 +1,10 @@ -import { SeedClient } from '@atproto/dev-env' +import { SeedClient, TestBsky } from '@atproto/dev-env' import { ids } from '../../src/lexicon/lexicons' import usersSeed from './users' export default async ( sc: SeedClient, - opts?: { inviteCode?: string; addModLabels?: boolean }, + opts?: { inviteCode?: string; addModLabels?: TestBsky }, ) => { await usersSeed(sc, opts) @@ -134,24 +134,7 @@ export default async ( await sc.repost(dan, alicesReplyToBob.ref) if (opts?.addModLabels) { - await sc.agent.com.atproto.admin.emitModerationEvent( - { - event: { - createLabelVals: ['repo-action-label'], - negateLabelVals: [], - $type: 'com.atproto.admin.defs#modEventLabel', - }, - subject: { - $type: 'com.atproto.admin.defs#repoRef', - did: dan, - }, - createdBy: 'did:example:admin', - }, - { - encoding: 'application/json', - headers: sc.adminAuthHeaders(), - }, - ) + await createLabel(opts.addModLabels, { did: dan, val: 'repo-action-label' }) } return sc @@ -169,3 +152,20 @@ export const replies = { bob: ['hear that label_me label_me_2'], carol: ['of course'], } + +const createLabel = async ( + bsky: TestBsky, + opts: { did: string; val: string }, +) => { + await bsky.db.db + .insertInto('label') + .values({ + uri: opts.did, + cid: '', + val: opts.val, + cts: new Date().toISOString(), + neg: false, + src: 'did:example:labeler', + }) + .execute() +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fba3901ac38..8ec8d78ae3b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + importers: .: @@ -191,6 +195,9 @@ importers: '@connectrpc/connect': specifier: ^1.1.4 version: 1.3.0(@bufbuild/protobuf@1.6.0) + '@connectrpc/connect-express': + specifier: ^1.1.4 + version: 1.3.0(@bufbuild/protobuf@1.6.0)(@connectrpc/connect-node@1.3.0)(@connectrpc/connect@1.3.0) '@connectrpc/connect-node': specifier: ^1.1.4 version: 1.3.0(@bufbuild/protobuf@1.6.0)(@connectrpc/connect@1.3.0) @@ -300,6 +307,9 @@ importers: axios: specifier: ^0.27.2 version: 0.27.2 + http2-express-bridge: + specifier: ^1.0.7 + version: 1.0.7 packages/bsync: dependencies: @@ -931,12 +941,12 @@ importers: services/bsky: dependencies: - '@atproto/aws': - specifier: workspace:^ - version: link:../../packages/aws '@atproto/bsky': specifier: workspace:^ version: link:../../packages/bsky + '@atproto/crypto': + specifier: workspace:^ + version: link:../../packages/crypto dd-trace: specifier: 3.13.2 version: 3.13.2 @@ -7227,10 +7237,19 @@ packages: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} + /depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + dev: true + /depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + /destroy@1.0.4: + resolution: {integrity: sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==} + dev: true + /destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -8479,6 +8498,17 @@ packages: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} dev: true + /http-errors@1.8.1: + resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} + engines: {node: '>= 0.6'} + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 1.5.0 + toidentifier: 1.0.1 + dev: true + /http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -8509,6 +8539,17 @@ packages: roarr: 7.15.1 type-fest: 2.19.0 + /http2-express-bridge@1.0.7: + resolution: {integrity: sha512-bmzZSyn3nuzXRqs/+WgH7IGOQYMCIZNJeqTJ/1AoDgMPTSP5wXQCxPGsdUbGzzxwiHrMwyT4Z7t8ccbsKqiHrw==} + engines: {node: '>= 10.0.0'} + dependencies: + merge-descriptors: 1.0.1 + send: 0.17.2 + setprototypeof: 1.2.0 + transitivePeerDependencies: + - supports-color + dev: true + /https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -10035,6 +10076,13 @@ packages: /on-exit-leak-free@2.1.0: resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==} + /on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 + dev: true + /on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -10889,6 +10937,27 @@ packages: dependencies: lru-cache: 6.0.0 + /send@0.17.2: + resolution: {integrity: sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==} + engines: {node: '>= 0.8.0'} + dependencies: + debug: 2.6.9 + depd: 1.1.2 + destroy: 1.0.4 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 1.8.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.3.0 + range-parser: 1.2.1 + statuses: 1.5.0 + transitivePeerDependencies: + - supports-color + dev: true + /send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} engines: {node: '>= 0.8.0'} @@ -11122,6 +11191,11 @@ packages: /standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + /statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + dev: true + /statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -11975,7 +12049,3 @@ packages: /zod@3.21.4: resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false diff --git a/services/bsky/Dockerfile b/services/bsky/Dockerfile index 9da764ecc3d..84422945ae0 100644 --- a/services/bsky/Dockerfile +++ b/services/bsky/Dockerfile @@ -1,4 +1,4 @@ -FROM node:18-alpine as build +FROM node:20.11-alpine as build RUN npm install -g pnpm @@ -8,7 +8,6 @@ COPY ./*.* ./ # NOTE bsky's transitive dependencies go here: if that changes, this needs to be updated. COPY ./packages/bsky ./packages/bsky COPY ./packages/api ./packages/api -COPY ./packages/aws ./packages/aws COPY ./packages/common ./packages/common COPY ./packages/common-web ./packages/common-web COPY ./packages/crypto ./packages/crypto @@ -34,7 +33,7 @@ RUN pnpm install --prod --shamefully-hoist --frozen-lockfile --prefer-offline > WORKDIR services/bsky # Uses assets from build stage to reduce build size -FROM node:18-alpine +FROM node:20.11-alpine RUN apk add --update dumb-init diff --git a/services/bsky/README.md b/services/bsky/README.md new file mode 100644 index 00000000000..fea1bf3c602 --- /dev/null +++ b/services/bsky/README.md @@ -0,0 +1,23 @@ +# bsky appview service + +This is the service entrypoint for the bsky appview. The entrypoint command should run `api.js` with node, e.g. `node api.js`. The following env vars are supported: + +- `BSKY_PUBLIC_URL` - (required) the public url of the appview, e.g. `https://api.bsky.app`. +- `BSKY_DID_PLC_URL` - (required) the url of the PLC service used for looking up did documents, e.g. `https://plc.directory`. +- `BSKY_DATAPLANE_URL` - (required) the url where the backing dataplane service lives. +- `BSKY_SERVICE_SIGNING_KEY` - (required) the public signing key in the form of a `did:key`, used for service-to-service auth. Advertised in the appview's `did:web`` document. +- `BSKY_ADMIN_PASSWORDS` - (alt. `BSKY_ADMIN_PASSWORD`) (required) comma-separated list of admin passwords used for role-based auth. +- `NODE_ENV` - (recommended) for production usage, should be set to `production`. Otherwise all responses are validated on their way out. There may be other effects of not setting this to `production`, as dependencies may also implement debug modes based on its value. +- `BSKY_VERSION` - (recommended) version of the bsky service. This is advertised by the health endpoint. +- `BSKY_PORT` - (recommended) the port that the service will run on. +- `BSKY_IMG_URI_ENDPOINT` - (recommended) the base url for resized images, e.g. `https://cdn.bsky.app/img`. When not set, sets-up an image resizing service directly on the appview. +- `BSKY_SERVER_DID` - (recommended) the did of the appview service. When this is a `did:web` that matches the appview's public url, a `did:web` document is served. +- `BSKY_HANDLE_RESOLVE_NAMESERVERS` - alternative domain name servers used for handle resolution, comma-separated. +- `BSKY_BLOB_CACHE_LOC` - when `BSKY_IMG_URI_ENDPOINT` is not set, this determines where resized blobs are cached by the image resizing service. +- `BSKY_COURIER_URL` - URL of courier service. +- `BSKY_COURIER_API_KEY` - API key for courier service. +- `BSKY_BSYNC_URL` - URL of bsync service. +- `BSKY_BSYNC_API_KEY` - API key for bsync service. +- `BSKY_SEARCH_URL` - (alt. `BSKY_SEARCH_ENDPOINT`) - +- `BSKY_LABELS_FROM_ISSUER_DIDS` - comma-separated list of labelers to always use for record labels. +- `MOD_SERVICE_DID` - the DID of the mod service, used to receive service authed requests. diff --git a/services/bsky/api.js b/services/bsky/api.js index 230efd7fbf3..44d8f96b37d 100644 --- a/services/bsky/api.js +++ b/services/bsky/api.js @@ -1,8 +1,14 @@ 'use strict' /* eslint-disable */ -require('dd-trace') // Only works with commonjs - .init({ logInjection: true }) - .tracer.use('express', { +const dd = require('dd-trace') + +dd.tracer + .init() + .use('http2', { + client: true, // calls into dataplane + server: false, + }) + .use('express', { hooks: { request: (span, req) => { maintainXrpcResource(span, req) @@ -10,125 +16,39 @@ require('dd-trace') // Only works with commonjs }, }) +// modify tracer in order to track calls to dataplane as a service with proper resource names +const DATAPLANE_PREFIX = '/bsky.Service/' +const origStartSpan = dd.tracer._tracer.startSpan +dd.tracer._tracer.startSpan = function (name, options) { + if ( + name !== 'http.request' || + options?.tags?.component !== 'http2' || + !options?.tags?.['http.url'] + ) { + return origStartSpan.call(this, name, options) + } + const uri = new URL(options.tags['http.url']) + if (!uri.pathname.startsWith(DATAPLANE_PREFIX)) { + return origStartSpan.call(this, name, options) + } + options.tags['service.name'] = 'dataplane-bsky' + options.tags['resource.name'] = uri.pathname.slice(DATAPLANE_PREFIX.length) + return origStartSpan.call(this, name, options) +} + // Tracer code above must come before anything else -const path = require('path') -const assert = require('assert') +const path = require('node:path') +const assert = require('node:assert') const cluster = require('cluster') -const { - BunnyInvalidator, - CloudfrontInvalidator, - MultiImageInvalidator, -} = require('@atproto/aws') const { Secp256k1Keypair } = require('@atproto/crypto') -const { - DatabaseCoordinator, - PrimaryDatabase, - Redis, - ServerConfig, - BskyAppView, -} = require('@atproto/bsky') +const { ServerConfig, BskyAppView } = require('@atproto/bsky') const main = async () => { const env = getEnv() - assert(env.dbPrimaryPostgresUrl, 'missing configuration for db') - - if (env.enableMigrations) { - // separate db needed for more permissions - const migrateDb = new PrimaryDatabase({ - url: env.dbMigratePostgresUrl, - schema: env.dbPostgresSchema, - poolSize: 2, - }) - await migrateDb.migrateToLatestOrThrow() - await migrateDb.close() - } - - const db = new DatabaseCoordinator({ - schema: env.dbPostgresSchema, - primary: { - url: env.dbPrimaryPostgresUrl, - poolSize: env.dbPrimaryPoolSize || env.dbPoolSize, - poolMaxUses: env.dbPoolMaxUses, - poolIdleTimeoutMs: env.dbPoolIdleTimeoutMs, - }, - replicas: env.dbReplicaPostgresUrls?.map((url, i) => { - return { - url, - poolSize: env.dbPoolSize, - poolMaxUses: env.dbPoolMaxUses, - poolIdleTimeoutMs: env.dbPoolIdleTimeoutMs, - tags: getTagsForIdx(env.dbReplicaTags, i), - } - }), - }) - const cfg = ServerConfig.readEnv({ - port: env.port, - version: env.version, - dbPrimaryPostgresUrl: env.dbPrimaryPostgresUrl, - dbReplicaPostgresUrls: env.dbReplicaPostgresUrls, - dbReplicaTags: env.dbReplicaTags, - dbPostgresSchema: env.dbPostgresSchema, - publicUrl: env.publicUrl, - didPlcUrl: env.didPlcUrl, - imgUriSalt: env.imgUriSalt, - imgUriKey: env.imgUriKey, - imgUriEndpoint: env.imgUriEndpoint, - blobCacheLocation: env.blobCacheLocation, - }) - - const redis = new Redis( - cfg.redisSentinelName - ? { - sentinel: cfg.redisSentinelName, - hosts: cfg.redisSentinelHosts, - password: cfg.redisPassword, - db: 1, - commandTimeout: 500, - } - : { - host: cfg.redisHost, - password: cfg.redisPassword, - db: 1, - commandTimeout: 500, - }, - ) - + const config = ServerConfig.readEnv() + assert(env.serviceSigningKey, 'must set BSKY_SERVICE_SIGNING_KEY') const signingKey = await Secp256k1Keypair.import(env.serviceSigningKey) - - // configure zero, one, or more image invalidators - const imgInvalidators = [] - - if (env.bunnyAccessKey) { - imgInvalidators.push( - new BunnyInvalidator({ - accessKey: env.bunnyAccessKey, - urlPrefix: cfg.imgUriEndpoint, - }), - ) - } - - if (env.cfDistributionId) { - imgInvalidators.push( - new CloudfrontInvalidator({ - distributionId: env.cfDistributionId, - pathPrefix: cfg.imgUriEndpoint && new URL(cfg.imgUriEndpoint).pathname, - }), - ) - } - - const imgInvalidator = - imgInvalidators.length > 1 - ? new MultiImageInvalidator(imgInvalidators) - : imgInvalidators[0] - - const bsky = BskyAppView.create({ - db, - redis, - signingKey, - config: cfg, - imgInvalidator, - }) - + const bsky = BskyAppView.create({ config, signingKey }) await bsky.start() // Graceful shutdown (see also https://aws.amazon.com/blogs/containers/graceful-shutdowns-with-ecs/) const shutdown = async () => { @@ -139,63 +59,14 @@ const main = async () => { } const getEnv = () => ({ - enableMigrations: process.env.ENABLE_MIGRATIONS === 'true', - port: parseInt(process.env.PORT), - version: process.env.BSKY_VERSION, - dbMigratePostgresUrl: - process.env.DB_MIGRATE_POSTGRES_URL || process.env.DB_PRIMARY_POSTGRES_URL, - dbPrimaryPostgresUrl: process.env.DB_PRIMARY_POSTGRES_URL, - dbPrimaryPoolSize: maybeParseInt(process.env.DB_PRIMARY_POOL_SIZE), - dbReplicaPostgresUrls: process.env.DB_REPLICA_POSTGRES_URLS - ? process.env.DB_REPLICA_POSTGRES_URLS.split(',') - : undefined, - dbReplicaTags: { - '*': getTagIdxs(process.env.DB_REPLICA_TAGS_ANY), // e.g. DB_REPLICA_TAGS_ANY=0,1 - timeline: getTagIdxs(process.env.DB_REPLICA_TAGS_TIMELINE), - feed: getTagIdxs(process.env.DB_REPLICA_TAGS_FEED), - search: getTagIdxs(process.env.DB_REPLICA_TAGS_SEARCH), - thread: getTagIdxs(process.env.DB_REPLICA_TAGS_THREAD), - }, - dbPostgresSchema: process.env.DB_POSTGRES_SCHEMA || undefined, - dbPoolSize: maybeParseInt(process.env.DB_POOL_SIZE), - dbPoolMaxUses: maybeParseInt(process.env.DB_POOL_MAX_USES), - dbPoolIdleTimeoutMs: maybeParseInt(process.env.DB_POOL_IDLE_TIMEOUT_MS), - serviceSigningKey: process.env.SERVICE_SIGNING_KEY, - publicUrl: process.env.PUBLIC_URL, - didPlcUrl: process.env.DID_PLC_URL, - imgUriSalt: process.env.IMG_URI_SALT, - imgUriKey: process.env.IMG_URI_KEY, - imgUriEndpoint: process.env.IMG_URI_ENDPOINT, - blobCacheLocation: process.env.BLOB_CACHE_LOC, - bunnyAccessKey: process.env.BUNNY_ACCESS_KEY, - cfDistributionId: process.env.CF_DISTRIBUTION_ID, - feedPublisherDid: process.env.FEED_PUBLISHER_DID, + serviceSigningKey: process.env.BSKY_SERVICE_SIGNING_KEY || undefined, }) -/** - * @param {Record} tags - * @param {number} idx - */ -const getTagsForIdx = (tagMap, idx) => { - const tags = [] - for (const [tag, indexes] of Object.entries(tagMap)) { - if (indexes.includes(idx)) { - tags.push(tag) - } - } - return tags -} - -/** - * @param {string} str - */ -const getTagIdxs = (str) => { - return str ? str.split(',').map((item) => parseInt(item, 10)) : [] -} - const maybeParseInt = (str) => { - const parsed = parseInt(str) - return isNaN(parsed) ? undefined : parsed + if (!str) return + const int = parseInt(str, 10) + if (isNaN(int)) return + return int } const maintainXrpcResource = (span, req) => { diff --git a/services/bsky/daemon.js b/services/bsky/daemon.js deleted file mode 100644 index 38b2fdb59e4..00000000000 --- a/services/bsky/daemon.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict' /* eslint-disable */ - -require('dd-trace/init') // Only works with commonjs - -// Tracer code above must come before anything else -const { PrimaryDatabase, DaemonConfig, BskyDaemon } = require('@atproto/bsky') - -const main = async () => { - const env = getEnv() - const db = new PrimaryDatabase({ - url: env.dbPostgresUrl, - schema: env.dbPostgresSchema, - poolSize: env.dbPoolSize, - poolMaxUses: env.dbPoolMaxUses, - poolIdleTimeoutMs: env.dbPoolIdleTimeoutMs, - }) - const cfg = DaemonConfig.readEnv({ - version: env.version, - dbPostgresUrl: env.dbPostgresUrl, - dbPostgresSchema: env.dbPostgresSchema, - notificationsDaemonFromDid: env.notificationsDaemonFromDid, - }) - const daemon = BskyDaemon.create({ db, cfg }) - await daemon.start() - process.on('SIGTERM', async () => { - await daemon.destroy() - }) -} - -const getEnv = () => ({ - version: process.env.BSKY_VERSION, - dbPostgresUrl: - process.env.DB_PRIMARY_POSTGRES_URL || process.env.DB_POSTGRES_URL, - dbPostgresSchema: process.env.DB_POSTGRES_SCHEMA || undefined, - dbPoolSize: maybeParseInt(process.env.DB_POOL_SIZE), - dbPoolMaxUses: maybeParseInt(process.env.DB_POOL_MAX_USES), - dbPoolIdleTimeoutMs: maybeParseInt(process.env.DB_POOL_IDLE_TIMEOUT_MS), - notificationsDaemonFromDid: - process.env.BSKY_NOTIFS_DAEMON_FROM_DID || undefined, -}) - -const maybeParseInt = (str) => { - const parsed = parseInt(str) - return isNaN(parsed) ? undefined : parsed -} - -main() diff --git a/services/bsky/indexer.js b/services/bsky/indexer.js deleted file mode 100644 index c7327339ff2..00000000000 --- a/services/bsky/indexer.js +++ /dev/null @@ -1,127 +0,0 @@ -'use strict' /* eslint-disable */ - -require('dd-trace/init') // Only works with commonjs - -// Tracer code above must come before anything else -const { CloudfrontInvalidator, BunnyInvalidator } = require('@atproto/aws') -const { - IndexerConfig, - BskyIndexer, - Redis, - PrimaryDatabase, -} = require('@atproto/bsky') - -const main = async () => { - const env = getEnv() - const db = new PrimaryDatabase({ - url: env.dbPostgresUrl, - schema: env.dbPostgresSchema, - poolSize: env.dbPoolSize, - poolMaxUses: env.dbPoolMaxUses, - poolIdleTimeoutMs: env.dbPoolIdleTimeoutMs, - }) - const cfg = IndexerConfig.readEnv({ - version: env.version, - dbPostgresUrl: env.dbPostgresUrl, - dbPostgresSchema: env.dbPostgresSchema, - }) - - // configure zero, one, or both image invalidators - let imgInvalidator - const bunnyInvalidator = env.bunnyAccessKey - ? new BunnyInvalidator({ - accessKey: env.bunnyAccessKey, - urlPrefix: cfg.imgUriEndpoint, - }) - : undefined - const cfInvalidator = env.cfDistributionId - ? new CloudfrontInvalidator({ - distributionId: env.cfDistributionId, - pathPrefix: cfg.imgUriEndpoint && new URL(cfg.imgUriEndpoint).pathname, - }) - : undefined - if (bunnyInvalidator && imgInvalidator) { - imgInvalidator = new MultiImageInvalidator([ - bunnyInvalidator, - imgInvalidator, - ]) - } else if (bunnyInvalidator) { - imgInvalidator = bunnyInvalidator - } else if (cfInvalidator) { - imgInvalidator = cfInvalidator - } - - const redis = new Redis( - cfg.redisSentinelName - ? { - sentinel: cfg.redisSentinelName, - hosts: cfg.redisSentinelHosts, - password: cfg.redisPassword, - } - : { - host: cfg.redisHost, - password: cfg.redisPassword, - }, - ) - - const redisCache = new Redis( - cfg.redisSentinelName - ? { - sentinel: cfg.redisSentinelName, - hosts: cfg.redisSentinelHosts, - password: cfg.redisPassword, - db: 1, - } - : { - host: cfg.redisHost, - password: cfg.redisPassword, - db: 1, - }, - ) - - const indexer = BskyIndexer.create({ - db, - redis, - redisCache, - cfg, - imgInvalidator, - }) - await indexer.start() - process.on('SIGTERM', async () => { - await indexer.destroy() - }) -} - -// Also accepts the following in readEnv(): -// - REDIS_HOST -// - REDIS_SENTINEL_NAME -// - REDIS_SENTINEL_HOSTS -// - REDIS_PASSWORD -// - DID_PLC_URL -// - DID_CACHE_STALE_TTL -// - DID_CACHE_MAX_TTL -// - LABELER_DID -// - HIVE_API_KEY -// - INDEXER_PARTITION_IDS -// - INDEXER_PARTITION_BATCH_SIZE -// - INDEXER_CONCURRENCY -// - INDEXER_SUB_LOCK_ID -const getEnv = () => ({ - version: process.env.BSKY_VERSION, - dbPostgresUrl: - process.env.DB_PRIMARY_POSTGRES_URL || process.env.DB_POSTGRES_URL, - dbPostgresSchema: process.env.DB_POSTGRES_SCHEMA || undefined, - dbPoolSize: maybeParseInt(process.env.DB_POOL_SIZE), - dbPoolMaxUses: maybeParseInt(process.env.DB_POOL_MAX_USES), - dbPoolIdleTimeoutMs: maybeParseInt(process.env.DB_POOL_IDLE_TIMEOUT_MS), - bunnyAccessKey: process.env.BUNNY_ACCESS_KEY, - cfDistributionId: process.env.CF_DISTRIBUTION_ID, - imgUriEndpoint: process.env.IMG_URI_ENDPOINT, -}) - -const maybeParseInt = (str) => { - const parsed = parseInt(str) - return isNaN(parsed) ? undefined : parsed -} - -main() diff --git a/services/bsky/ingester.js b/services/bsky/ingester.js deleted file mode 100644 index 19c33ea1067..00000000000 --- a/services/bsky/ingester.js +++ /dev/null @@ -1,75 +0,0 @@ -'use strict' /* eslint-disable */ - -require('dd-trace/init') // Only works with commonjs - -// Tracer code above must come before anything else -const { - PrimaryDatabase, - IngesterConfig, - BskyIngester, - Redis, -} = require('@atproto/bsky') - -const main = async () => { - const env = getEnv() - // No migration: ingester only uses pg for a lock - const db = new PrimaryDatabase({ - url: env.dbPostgresUrl, - schema: env.dbPostgresSchema, - poolSize: env.dbPoolSize, - poolMaxUses: env.dbPoolMaxUses, - poolIdleTimeoutMs: env.dbPoolIdleTimeoutMs, - }) - const cfg = IngesterConfig.readEnv({ - version: env.version, - dbPostgresUrl: env.dbPostgresUrl, - dbPostgresSchema: env.dbPostgresSchema, - repoProvider: env.repoProvider, - ingesterSubLockId: env.subLockId, - }) - const redis = new Redis( - cfg.redisSentinelName - ? { - sentinel: cfg.redisSentinelName, - hosts: cfg.redisSentinelHosts, - password: cfg.redisPassword, - } - : { - host: cfg.redisHost, - password: cfg.redisPassword, - }, - ) - const ingester = BskyIngester.create({ db, redis, cfg }) - await ingester.start() - process.on('SIGTERM', async () => { - await ingester.destroy() - }) -} - -// Also accepts the following in readEnv(): -// - REDIS_HOST -// - REDIS_SENTINEL_NAME -// - REDIS_SENTINEL_HOSTS -// - REDIS_PASSWORD -// - REPO_PROVIDER -// - INGESTER_PARTITION_COUNT -// - INGESTER_MAX_ITEMS -// - INGESTER_CHECK_ITEMS_EVERY_N -// - INGESTER_INITIAL_CURSOR -// - INGESTER_SUB_LOCK_ID -const getEnv = () => ({ - version: process.env.BSKY_VERSION, - dbPostgresUrl: - process.env.DB_PRIMARY_POSTGRES_URL || process.env.DB_POSTGRES_URL, - dbPostgresSchema: process.env.DB_POSTGRES_SCHEMA || undefined, - dbPoolSize: maybeParseInt(process.env.DB_POOL_SIZE), - dbPoolMaxUses: maybeParseInt(process.env.DB_POOL_MAX_USES), - dbPoolIdleTimeoutMs: maybeParseInt(process.env.DB_POOL_IDLE_TIMEOUT_MS), -}) - -const maybeParseInt = (str) => { - const parsed = parseInt(str) - return isNaN(parsed) ? undefined : parsed -} - -main() diff --git a/services/bsky/package.json b/services/bsky/package.json index 65de10674dc..c1feff5b40b 100644 --- a/services/bsky/package.json +++ b/services/bsky/package.json @@ -2,8 +2,8 @@ "name": "bsky-app-view-service", "private": true, "dependencies": { - "@atproto/aws": "workspace:^", "@atproto/bsky": "workspace:^", + "@atproto/crypto": "workspace:^", "dd-trace": "3.13.2" } } From 54247769a3a2a4c18e9f46499dc0360ab97f438b Mon Sep 17 00:00:00 2001 From: Foysal Ahamed Date: Tue, 27 Feb 2024 21:35:56 +0100 Subject: [PATCH 035/103] Allow only admins to takedown feed gens (#2228) * :lock: Only allow admins to reverse/take down feed gens * :recycle: Remove unnecessary auth level check * :white_check_mark: Update test expectation --- .../src/api/admin/emitModerationEvent.ts | 26 ++++++++++++------- packages/ozone/tests/moderation.test.ts | 2 +- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/ozone/src/api/admin/emitModerationEvent.ts b/packages/ozone/src/api/admin/emitModerationEvent.ts index c04dfbecae6..ef4c5fd2822 100644 --- a/packages/ozone/src/api/admin/emitModerationEvent.ts +++ b/packages/ozone/src/api/admin/emitModerationEvent.ts @@ -27,17 +27,23 @@ export default function (server: Server, ctx: AppContext) { // apply access rules - // if less than moderator access then can not takedown an account - if (!access.moderator && isTakedownEvent && subject.isRepo()) { - throw new AuthRequiredError( - 'Must be a full moderator to perform an account takedown', - ) - } // if less than moderator access then can only take ack and escalation actions - if (!access.moderator && (isTakedownEvent || isReverseTakedownEvent)) { - throw new AuthRequiredError( - 'Must be a full moderator to take this type of action', - ) + if (isTakedownEvent || isReverseTakedownEvent) { + if (!access.moderator) { + throw new AuthRequiredError( + 'Must be a full moderator to take this type of action', + ) + } + + // Non admins should not be able to take down feed generators + if ( + !access.admin && + subject.recordPath?.includes('app.bsky.feed.generator/') + ) { + throw new AuthRequiredError( + 'Must be a full admin to take this type of action on feed generators', + ) + } } // if less than moderator access then can not apply labels if (!access.moderator && isLabelEvent) { diff --git a/packages/ozone/tests/moderation.test.ts b/packages/ozone/tests/moderation.test.ts index 79aae3938c9..790d0f5f820 100644 --- a/packages/ozone/tests/moderation.test.ts +++ b/packages/ozone/tests/moderation.test.ts @@ -779,7 +779,7 @@ describe('moderation', () => { }, ) await expect(attemptTakedownTriage).rejects.toThrow( - 'Must be a full moderator to perform an account takedown', + 'Must be a full moderator to take this type of action', ) }) From b3434d4e548d1efe1b11d4a157588859b6142c20 Mon Sep 17 00:00:00 2001 From: Daniel Holmgren Date: Wed, 28 Feb 2024 17:26:13 -0600 Subject: [PATCH 036/103] Ozone label endpoints (#2043) * sketching out label sequencer * refactor sequencer * sequencer tests * tests * add query labels endpoint & tests * add pagination * fix label formatting on temp * tidy * format labels * make use listen/notify for sequencer * fix hanging server test * Update packages/ozone/src/api/label/queryLabels.ts Co-authored-by: devin ivy * pr tidy --------- Co-authored-by: devin ivy --- packages/ozone/src/api/index.ts | 4 + packages/ozone/src/api/label/queryLabels.ts | 58 +++++ .../ozone/src/api/label/subscribeLabels.ts | 25 ++ packages/ozone/src/api/temp/fetchLabels.ts | 6 +- packages/ozone/src/config/config.ts | 6 + packages/ozone/src/config/env.ts | 6 + packages/ozone/src/context.ts | 12 + .../db/migrations/20231219T205730722Z-init.ts | 8 +- packages/ozone/src/db/schema/label.ts | 7 + packages/ozone/src/index.ts | 2 + packages/ozone/src/logger.ts | 2 + packages/ozone/src/mod-service/index.ts | 145 ++++++------ packages/ozone/src/mod-service/util.ts | 13 + packages/ozone/src/mod-service/views.ts | 7 +- packages/ozone/src/sequencer/index.ts | 2 + packages/ozone/src/sequencer/outbox.ts | 122 ++++++++++ packages/ozone/src/sequencer/sequencer.ts | 143 +++++++++++ packages/ozone/tests/query-labels.test.ts | 124 ++++++++++ packages/ozone/tests/sequencer.test.ts | 222 ++++++++++++++++++ packages/ozone/tests/server.test.ts | 2 + 20 files changed, 834 insertions(+), 82 deletions(-) create mode 100644 packages/ozone/src/api/label/queryLabels.ts create mode 100644 packages/ozone/src/api/label/subscribeLabels.ts create mode 100644 packages/ozone/src/mod-service/util.ts create mode 100644 packages/ozone/src/sequencer/index.ts create mode 100644 packages/ozone/src/sequencer/outbox.ts create mode 100644 packages/ozone/src/sequencer/sequencer.ts create mode 100644 packages/ozone/tests/query-labels.test.ts create mode 100644 packages/ozone/tests/sequencer.test.ts diff --git a/packages/ozone/src/api/index.ts b/packages/ozone/src/api/index.ts index 49a9d70e1fa..54e52ffe292 100644 --- a/packages/ozone/src/api/index.ts +++ b/packages/ozone/src/api/index.ts @@ -8,6 +8,8 @@ import getRepo from './admin/getRepo' import queryModerationStatuses from './admin/queryModerationStatuses' import queryModerationEvents from './admin/queryModerationEvents' import getModerationEvent from './admin/getModerationEvent' +import queryLabels from './label/queryLabels' +import subscribeLabels from './label/subscribeLabels' import fetchLabels from './temp/fetchLabels' import createCommunicationTemplate from './admin/createCommunicationTemplate' import updateCommunicationTemplate from './admin/updateCommunicationTemplate' @@ -27,6 +29,8 @@ export default function (server: Server, ctx: AppContext) { getModerationEvent(server, ctx) queryModerationEvents(server, ctx) queryModerationStatuses(server, ctx) + queryLabels(server, ctx) + subscribeLabels(server, ctx) fetchLabels(server, ctx) listCommunicationTemplates(server, ctx) createCommunicationTemplate(server, ctx) diff --git a/packages/ozone/src/api/label/queryLabels.ts b/packages/ozone/src/api/label/queryLabels.ts new file mode 100644 index 00000000000..6de6380d194 --- /dev/null +++ b/packages/ozone/src/api/label/queryLabels.ts @@ -0,0 +1,58 @@ +import { Server } from '../../lexicon' +import AppContext from '../../context' +import { InvalidRequestError } from '@atproto/xrpc-server' +import { sql } from 'kysely' +import { formatLabel } from '../../mod-service/util' + +export default function (server: Server, ctx: AppContext) { + server.com.atproto.label.queryLabels(async ({ params }) => { + const { uriPatterns, sources, limit, cursor } = params + let builder = ctx.db.db.selectFrom('label').selectAll().limit(limit) + // if includes '*', then we don't need a where clause + if (!uriPatterns.includes('*')) { + builder = builder.where((qb) => { + // starter where clause that is always false so that we can chain `orWhere`s + qb = qb.where(sql`1 = 0`) + for (const pattern of uriPatterns) { + // if no '*', then we're looking for an exact match + if (!pattern.includes('*')) { + qb = qb.orWhere('uri', '=', pattern) + } else { + if (pattern.indexOf('*') < pattern.length - 1) { + throw new InvalidRequestError(`invalid pattern: ${pattern}`) + } + const searchPattern = pattern + .slice(0, -1) + .replaceAll('%', '') // sanitize search pattern + .replaceAll('_', '\\_') // escape any underscores + qb = qb.orWhere('uri', 'like', `${searchPattern}%`) + } + } + return qb + }) + } + if (sources && sources.length > 0) { + builder = builder.where('src', 'in', sources) + } + if (cursor) { + const cursorId = parseInt(cursor, 10) + if (isNaN(cursorId)) { + throw new InvalidRequestError('invalid cursor') + } + builder = builder.where('id', '>', cursorId) + } + + const res = await builder.execute() + + const labels = res.map((l) => formatLabel(l)) + const resCursor = res.at(-1)?.id.toString(10) + + return { + encoding: 'application/json', + body: { + cursor: resCursor, + labels, + }, + } + }) +} diff --git a/packages/ozone/src/api/label/subscribeLabels.ts b/packages/ozone/src/api/label/subscribeLabels.ts new file mode 100644 index 00000000000..7efb339d488 --- /dev/null +++ b/packages/ozone/src/api/label/subscribeLabels.ts @@ -0,0 +1,25 @@ +import { Server } from '../../lexicon' +import AppContext from '../../context' +import Outbox from '../../sequencer/outbox' +import { InvalidRequestError } from '@atproto/xrpc-server' + +export default function (server: Server, ctx: AppContext) { + server.com.atproto.label.subscribeLabels(async function* ({ + params, + signal, + }) { + const { cursor } = params + const outbox = new Outbox(ctx.sequencer) + + if (cursor !== undefined) { + const curr = await ctx.sequencer.curr() + if (cursor > (curr ?? 0)) { + throw new InvalidRequestError('Cursor in the future.', 'FutureCursor') + } + } + + for await (const evt of outbox.events(cursor, signal)) { + yield evt + } + }) +} diff --git a/packages/ozone/src/api/temp/fetchLabels.ts b/packages/ozone/src/api/temp/fetchLabels.ts index f11cb2028bb..fd0331487d1 100644 --- a/packages/ozone/src/api/temp/fetchLabels.ts +++ b/packages/ozone/src/api/temp/fetchLabels.ts @@ -1,5 +1,6 @@ import { Server } from '../../lexicon' import AppContext from '../../context' +import { formatLabel } from '../../mod-service/util' import { UNSPECCED_TAKEDOWN_BLOBS_LABEL, UNSPECCED_TAKEDOWN_LABEL, @@ -28,10 +29,7 @@ export default function (server: Server, ctx: AppContext) { .limit(limit) .execute() - const labels = labelRes.map((l) => ({ - ...l, - cid: l.cid === '' ? undefined : l.cid, - })) + const labels = labelRes.map((l) => formatLabel(l)) return { encoding: 'application/json', diff --git a/packages/ozone/src/config/config.ts b/packages/ozone/src/config/config.ts index caa799b2a90..32ed8ba5cb5 100644 --- a/packages/ozone/src/config/config.ts +++ b/packages/ozone/src/config/config.ts @@ -19,6 +19,9 @@ export const envToCfg = (env: OzoneEnvironment): OzoneConfig => { const dbCfg: OzoneConfig['db'] = { postgresUrl: env.dbPostgresUrl, postgresSchema: env.dbPostgresSchema, + poolSize: env.dbPoolSize, + poolMaxUses: env.dbPoolMaxUses, + poolIdleTimeoutMs: env.dbPoolIdleTimeoutMs, } assert(env.appviewUrl) @@ -67,6 +70,9 @@ export type ServiceConfig = { export type DatabaseConfig = { postgresUrl: string postgresSchema?: string + poolSize?: number + poolMaxUses?: number + poolIdleTimeoutMs?: number } export type AppviewConfig = { diff --git a/packages/ozone/src/config/env.ts b/packages/ozone/src/config/env.ts index 4f96ba63d53..b0ad10074eb 100644 --- a/packages/ozone/src/config/env.ts +++ b/packages/ozone/src/config/env.ts @@ -13,6 +13,9 @@ export const readEnv = (): OzoneEnvironment => { pdsDid: envStr('OZONE_PDS_DID'), dbPostgresUrl: envStr('OZONE_DB_POSTGRES_URL'), dbPostgresSchema: envStr('OZONE_DB_POSTGRES_SCHEMA'), + dbPoolSize: envInt('OZONE_DB_POOL_SIZE'), + dbPoolMaxUses: envInt('OZONE_DB_POOL_MAX_USES'), + dbPoolIdleTimeoutMs: envInt('OZONE_DB_POOL_IDLE_TIMEOUT_MS'), didPlcUrl: envStr('OZONE_DID_PLC_URL'), adminPassword: envStr('OZONE_ADMIN_PASSWORD'), moderatorPassword: envStr('OZONE_MODERATOR_PASSWORD'), @@ -33,6 +36,9 @@ export type OzoneEnvironment = { pdsDid?: string dbPostgresUrl?: string dbPostgresSchema?: string + dbPoolSize?: number + dbPoolMaxUses?: number + dbPoolIdleTimeoutMs?: number didPlcUrl?: string adminPassword?: string moderatorPassword?: string diff --git a/packages/ozone/src/context.ts b/packages/ozone/src/context.ts index 1cb0ec1bd83..00ef4bd71ba 100644 --- a/packages/ozone/src/context.ts +++ b/packages/ozone/src/context.ts @@ -10,6 +10,7 @@ import * as auth from './auth' import { BackgroundQueue } from './background' import assert from 'assert' import { EventPusher } from './daemon' +import Sequencer from './sequencer/sequencer' import { CommunicationTemplateService, CommunicationTemplateServiceCreator, @@ -25,6 +26,7 @@ export type AppContextOptions = { signingKey: Keypair idResolver: IdResolver backgroundQueue: BackgroundQueue + sequencer: Sequencer } export class AppContext { @@ -38,6 +40,9 @@ export class AppContext { const db = new Database({ url: cfg.db.postgresUrl, schema: cfg.db.postgresSchema, + poolSize: cfg.db.poolSize, + poolMaxUses: cfg.db.poolMaxUses, + poolIdleTimeoutMs: cfg.db.poolIdleTimeoutMs, }) const signingKey = await Secp256k1Keypair.import(secrets.signingKeyHex) const appviewAgent = new AtpAgent({ service: cfg.appview.url }) @@ -74,6 +79,8 @@ export class AppContext { plcUrl: cfg.identity.plcUrl, }) + const sequencer = new Sequencer(db) + return new AppContext( { db, @@ -85,6 +92,7 @@ export class AppContext { signingKey, idResolver, backgroundQueue, + sequencer, ...(overrides ?? {}), }, secrets, @@ -139,6 +147,10 @@ export class AppContext { return this.opts.backgroundQueue } + get sequencer(): Sequencer { + return this.opts.sequencer + } + get authVerifier() { return auth.authVerifier(this.idResolver, { aud: this.cfg.service.did }) } diff --git a/packages/ozone/src/db/migrations/20231219T205730722Z-init.ts b/packages/ozone/src/db/migrations/20231219T205730722Z-init.ts index f636f40a3f4..e08c80686d2 100644 --- a/packages/ozone/src/db/migrations/20231219T205730722Z-init.ts +++ b/packages/ozone/src/db/migrations/20231219T205730722Z-init.ts @@ -68,13 +68,19 @@ export async function up(db: Kysely): Promise { // Label await db.schema .createTable('label') + .addColumn('id', 'bigserial', (col) => col.primaryKey()) .addColumn('src', 'varchar', (col) => col.notNull()) .addColumn('uri', 'varchar', (col) => col.notNull()) .addColumn('cid', 'varchar', (col) => col.notNull()) .addColumn('val', 'varchar', (col) => col.notNull()) .addColumn('neg', 'boolean', (col) => col.notNull()) .addColumn('cts', 'varchar', (col) => col.notNull()) - .addPrimaryKeyConstraint('label_pkey', ['src', 'uri', 'cid', 'val']) + .execute() + await db.schema + .createIndex('unique_label_idx') + .unique() + .on('label') + .columns(['src', 'uri', 'cid', 'val']) .execute() await db.schema .createIndex('label_uri_index') diff --git a/packages/ozone/src/db/schema/label.ts b/packages/ozone/src/db/schema/label.ts index 0c8a398a7db..f50a6119ab3 100644 --- a/packages/ozone/src/db/schema/label.ts +++ b/packages/ozone/src/db/schema/label.ts @@ -1,6 +1,9 @@ +import { Generated, Selectable } from 'kysely' + export const tableName = 'label' export interface Label { + id: Generated src: string uri: string cid: string @@ -9,4 +12,8 @@ export interface Label { cts: string } +export type LabelRow = Selectable