Add app.bsky.graph.getAssertions() (#340)
* Add app.bsky.graph.getAssertions() * Update getAssertions to support query by author or subject * Revert accidental change to members test
This commit is contained in:
parent
962041e6c1
commit
a1bee406e2
lexicons/app/bsky/graph
packages
api/src/client
pds
src
api/app/bsky
lexicon
tests/views
90
lexicons/app/bsky/graph/getAssertions.json
Normal file
90
lexicons/app/bsky/graph/getAssertions.json
Normal file
@ -0,0 +1,90 @@
|
||||
{
|
||||
"lexicon": 1,
|
||||
"id": "app.bsky.graph.getAssertions",
|
||||
"type": "query",
|
||||
"description": "General-purpose query for assertions.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"author": {"type": "string"},
|
||||
"subject": {"type": "string"},
|
||||
"assertion": {"type": "string"},
|
||||
"confirmed": {"type": "boolean"},
|
||||
"limit": {"type": "number", "maximum": 100},
|
||||
"before": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"encoding": "application/json",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["assertions"],
|
||||
"properties": {
|
||||
"cursor": {"type": "string"},
|
||||
"assertions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["uri", "cid", "assertion", "author", "subject", "indexedAt", "createdAt"],
|
||||
"properties": {
|
||||
"uri": {"type": "string"},
|
||||
"cid": {"type": "string"},
|
||||
"assertion": {"type": "string"},
|
||||
"confirmation": {"$ref": "#/defs/confirmation"},
|
||||
"author": {"$ref": "#/defs/actor"},
|
||||
"subject": {"$ref": "#/defs/actor"},
|
||||
"indexedAt": {"type": "string", "format": "date-time"},
|
||||
"createdAt": {"type": "string", "format": "date-time"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defs": {
|
||||
"confirmation": {
|
||||
"type": "object",
|
||||
"required": ["uri", "cid", "indexedAt", "createdAt"],
|
||||
"properties": {
|
||||
"uri": {"type": "string"},
|
||||
"cid": {"type": "string"},
|
||||
"indexedAt": {"type": "string", "format": "date-time"},
|
||||
"createdAt": {"type": "string", "format": "date-time"}
|
||||
}
|
||||
},
|
||||
"actor": {
|
||||
"type": "object",
|
||||
"required": ["did", "declaration", "handle"],
|
||||
"properties": {
|
||||
"did": {"type": "string"},
|
||||
"declaration": {"$ref": "#/defs/declaration"},
|
||||
"handle": {"type": "string"},
|
||||
"displayName": {
|
||||
"type": "string",
|
||||
"maxLength": 64
|
||||
}
|
||||
}
|
||||
},
|
||||
"declaration": {
|
||||
"type": "object",
|
||||
"required": ["cid", "actorType"],
|
||||
"properties": {
|
||||
"cid": {"type": "string"},
|
||||
"actorType": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/defs/actorKnown"},
|
||||
{"$ref": "#/defs/actorUnknown"}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"actorKnown": {
|
||||
"type": "string",
|
||||
"enum": ["app.bsky.system.actorUser", "app.bsky.system.actorScene"]
|
||||
},
|
||||
"actorUnknown": {
|
||||
"type": "string",
|
||||
"not": {"enum": ["app.bsky.system.actorUser", "app.bsky.system.actorScene"]}
|
||||
}
|
||||
}
|
||||
}
|
@ -49,6 +49,7 @@ import * as AppBskyFeedVote from './types/app/bsky/feed/vote'
|
||||
import * as AppBskyGraphAssertion from './types/app/bsky/graph/assertion'
|
||||
import * as AppBskyGraphConfirmation from './types/app/bsky/graph/confirmation'
|
||||
import * as AppBskyGraphFollow from './types/app/bsky/graph/follow'
|
||||
import * as AppBskyGraphGetAssertions from './types/app/bsky/graph/getAssertions'
|
||||
import * as AppBskyGraphGetFollowers from './types/app/bsky/graph/getFollowers'
|
||||
import * as AppBskyGraphGetFollows from './types/app/bsky/graph/getFollows'
|
||||
import * as AppBskyGraphGetMembers from './types/app/bsky/graph/getMembers'
|
||||
@ -101,6 +102,7 @@ export * as AppBskyFeedVote from './types/app/bsky/feed/vote'
|
||||
export * as AppBskyGraphAssertion from './types/app/bsky/graph/assertion'
|
||||
export * as AppBskyGraphConfirmation from './types/app/bsky/graph/confirmation'
|
||||
export * as AppBskyGraphFollow from './types/app/bsky/graph/follow'
|
||||
export * as AppBskyGraphGetAssertions from './types/app/bsky/graph/getAssertions'
|
||||
export * as AppBskyGraphGetFollowers from './types/app/bsky/graph/getFollowers'
|
||||
export * as AppBskyGraphGetFollows from './types/app/bsky/graph/getFollows'
|
||||
export * as AppBskyGraphGetMembers from './types/app/bsky/graph/getMembers'
|
||||
@ -1043,6 +1045,17 @@ export class GraphNS {
|
||||
this.follow = new FollowRecord(service)
|
||||
}
|
||||
|
||||
getAssertions(
|
||||
params?: AppBskyGraphGetAssertions.QueryParams,
|
||||
opts?: AppBskyGraphGetAssertions.CallOptions
|
||||
): Promise<AppBskyGraphGetAssertions.Response> {
|
||||
return this._service.xrpc
|
||||
.call('app.bsky.graph.getAssertions', params, undefined, opts)
|
||||
.catch((e) => {
|
||||
throw AppBskyGraphGetAssertions.toKnownErr(e)
|
||||
})
|
||||
}
|
||||
|
||||
getFollowers(
|
||||
params?: AppBskyGraphGetFollowers.QueryParams,
|
||||
opts?: AppBskyGraphGetFollowers.CallOptions
|
||||
|
@ -3005,6 +3005,231 @@ export const methodSchemaDict: Record<string, MethodSchema> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
'app.bsky.graph.getAssertions': {
|
||||
lexicon: 1,
|
||||
id: 'app.bsky.graph.getAssertions',
|
||||
type: 'query',
|
||||
description: 'General-purpose query for assertions.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
author: {
|
||||
type: 'string',
|
||||
},
|
||||
subject: {
|
||||
type: 'string',
|
||||
},
|
||||
assertion: {
|
||||
type: 'string',
|
||||
},
|
||||
confirmed: {
|
||||
type: 'boolean',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
maximum: 100,
|
||||
},
|
||||
before: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
encoding: 'application/json',
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['assertions'],
|
||||
properties: {
|
||||
cursor: {
|
||||
type: 'string',
|
||||
},
|
||||
assertions: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
required: [
|
||||
'uri',
|
||||
'cid',
|
||||
'assertion',
|
||||
'author',
|
||||
'subject',
|
||||
'indexedAt',
|
||||
'createdAt',
|
||||
],
|
||||
properties: {
|
||||
uri: {
|
||||
type: 'string',
|
||||
},
|
||||
cid: {
|
||||
type: 'string',
|
||||
},
|
||||
assertion: {
|
||||
type: 'string',
|
||||
},
|
||||
confirmation: {
|
||||
$ref: '#/$defs/confirmation',
|
||||
},
|
||||
author: {
|
||||
$ref: '#/$defs/actor',
|
||||
},
|
||||
subject: {
|
||||
$ref: '#/$defs/actor',
|
||||
},
|
||||
indexedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
$defs: {
|
||||
confirmation: {
|
||||
type: 'object',
|
||||
required: ['uri', 'cid', 'indexedAt', 'createdAt'],
|
||||
properties: {
|
||||
uri: {
|
||||
type: 'string',
|
||||
},
|
||||
cid: {
|
||||
type: 'string',
|
||||
},
|
||||
indexedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
},
|
||||
},
|
||||
},
|
||||
actor: {
|
||||
type: 'object',
|
||||
required: ['did', 'declaration', 'handle'],
|
||||
properties: {
|
||||
did: {
|
||||
type: 'string',
|
||||
},
|
||||
declaration: {
|
||||
$ref: '#/$defs/declaration',
|
||||
},
|
||||
handle: {
|
||||
type: 'string',
|
||||
},
|
||||
displayName: {
|
||||
type: 'string',
|
||||
maxLength: 64,
|
||||
},
|
||||
},
|
||||
},
|
||||
declaration: {
|
||||
type: 'object',
|
||||
required: ['cid', 'actorType'],
|
||||
properties: {
|
||||
cid: {
|
||||
type: 'string',
|
||||
},
|
||||
actorType: {
|
||||
oneOf: [
|
||||
{
|
||||
$ref: '#/$defs/actorKnown',
|
||||
},
|
||||
{
|
||||
$ref: '#/$defs/actorUnknown',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
actorKnown: {
|
||||
type: 'string',
|
||||
enum: ['app.bsky.system.actorUser', 'app.bsky.system.actorScene'],
|
||||
},
|
||||
actorUnknown: {
|
||||
type: 'string',
|
||||
not: {
|
||||
enum: ['app.bsky.system.actorUser', 'app.bsky.system.actorScene'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
defs: {
|
||||
confirmation: {
|
||||
type: 'object',
|
||||
required: ['uri', 'cid', 'indexedAt', 'createdAt'],
|
||||
properties: {
|
||||
uri: {
|
||||
type: 'string',
|
||||
},
|
||||
cid: {
|
||||
type: 'string',
|
||||
},
|
||||
indexedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
},
|
||||
},
|
||||
},
|
||||
actor: {
|
||||
type: 'object',
|
||||
required: ['did', 'declaration', 'handle'],
|
||||
properties: {
|
||||
did: {
|
||||
type: 'string',
|
||||
},
|
||||
declaration: {
|
||||
$ref: '#/$defs/declaration',
|
||||
},
|
||||
handle: {
|
||||
type: 'string',
|
||||
},
|
||||
displayName: {
|
||||
type: 'string',
|
||||
maxLength: 64,
|
||||
},
|
||||
},
|
||||
},
|
||||
declaration: {
|
||||
type: 'object',
|
||||
required: ['cid', 'actorType'],
|
||||
properties: {
|
||||
cid: {
|
||||
type: 'string',
|
||||
},
|
||||
actorType: {
|
||||
oneOf: [
|
||||
{
|
||||
$ref: '#/$defs/actorKnown',
|
||||
},
|
||||
{
|
||||
$ref: '#/$defs/actorUnknown',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
actorKnown: {
|
||||
type: 'string',
|
||||
enum: ['app.bsky.system.actorUser', 'app.bsky.system.actorScene'],
|
||||
},
|
||||
actorUnknown: {
|
||||
type: 'string',
|
||||
not: {
|
||||
enum: ['app.bsky.system.actorUser', 'app.bsky.system.actorScene'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'app.bsky.graph.getFollowers': {
|
||||
lexicon: 1,
|
||||
id: 'app.bsky.graph.getFollowers',
|
||||
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* GENERATED CODE - DO NOT MODIFY
|
||||
*/
|
||||
import { Headers, XRPCError } from '@atproto/xrpc'
|
||||
|
||||
export interface QueryParams {
|
||||
author?: string;
|
||||
subject?: string;
|
||||
assertion?: string;
|
||||
confirmed?: boolean;
|
||||
limit?: number;
|
||||
before?: string;
|
||||
}
|
||||
|
||||
export interface CallOptions {
|
||||
headers?: Headers;
|
||||
}
|
||||
|
||||
export type InputSchema = undefined
|
||||
|
||||
export type ActorKnown =
|
||||
| 'app.bsky.system.actorUser'
|
||||
| 'app.bsky.system.actorScene'
|
||||
export type ActorUnknown = string
|
||||
|
||||
export interface OutputSchema {
|
||||
cursor?: string;
|
||||
assertions: {
|
||||
uri: string,
|
||||
cid: string,
|
||||
assertion: string,
|
||||
confirmation?: Confirmation,
|
||||
author: Actor,
|
||||
subject: Actor,
|
||||
indexedAt: string,
|
||||
createdAt: string,
|
||||
}[];
|
||||
}
|
||||
export interface Confirmation {
|
||||
uri: string;
|
||||
cid: string;
|
||||
indexedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
export interface Actor {
|
||||
did: string;
|
||||
declaration: Declaration;
|
||||
handle: string;
|
||||
displayName?: string;
|
||||
}
|
||||
export interface Declaration {
|
||||
cid: string;
|
||||
actorType: ActorKnown | ActorUnknown;
|
||||
}
|
||||
|
||||
export interface Response {
|
||||
success: boolean;
|
||||
headers: Headers;
|
||||
data: OutputSchema;
|
||||
}
|
||||
|
||||
export function toKnownErr(e: any) {
|
||||
if (e instanceof XRPCError) {
|
||||
}
|
||||
return e
|
||||
}
|
149
packages/pds/src/api/app/bsky/graph/getAssertions.ts
Normal file
149
packages/pds/src/api/app/bsky/graph/getAssertions.ts
Normal file
@ -0,0 +1,149 @@
|
||||
import { Server } from '../../../../lexicon'
|
||||
import { InvalidRequestError } from '@atproto/xrpc-server'
|
||||
import * as GetAssertions from '../../../../lexicon/types/app/bsky/graph/getAssertions'
|
||||
import { getActorInfo } from '../util'
|
||||
import * as locals from '../../../../locals'
|
||||
import { paginate } from '../../../../db/util'
|
||||
|
||||
export default function (server: Server) {
|
||||
server.app.bsky.graph.getAssertions(
|
||||
async (params: GetAssertions.QueryParams, _input, _req, res) => {
|
||||
const { author, subject, assertion, confirmed, limit, before } = params
|
||||
const { db } = locals.get(res)
|
||||
const { ref } = db.db.dynamic
|
||||
|
||||
if (!author && !subject) {
|
||||
throw new InvalidRequestError(`Must provide an author or subject`)
|
||||
}
|
||||
|
||||
const authorInfo =
|
||||
author &&
|
||||
(await getActorInfo(db.db, author).catch((_e) => {
|
||||
throw new InvalidRequestError(`Actor not found: ${author}`)
|
||||
}))
|
||||
const subjectInfo =
|
||||
subject &&
|
||||
(await getActorInfo(db.db, subject).catch((_e) => {
|
||||
throw new InvalidRequestError(`Actor not found: ${subject}`)
|
||||
}))
|
||||
|
||||
let assertionsReq = db.db
|
||||
.selectFrom('assertion')
|
||||
.if(typeof assertion === 'string', (q) =>
|
||||
q.where('assertion.assertion', '=', assertion as string),
|
||||
)
|
||||
.if(confirmed === true, (q) =>
|
||||
q.where('assertion.confirmUri', 'is not', null),
|
||||
)
|
||||
.if(confirmed === false, (q) =>
|
||||
q.where('assertion.confirmUri', 'is', null),
|
||||
)
|
||||
.innerJoin('did_handle as author', 'author.did', 'assertion.creator')
|
||||
.leftJoin(
|
||||
'profile as authorProfile',
|
||||
'authorProfile.creator',
|
||||
'author.did',
|
||||
)
|
||||
.innerJoin(
|
||||
'did_handle as subject',
|
||||
'subject.did',
|
||||
'assertion.subjectDid',
|
||||
)
|
||||
.leftJoin(
|
||||
'profile as subjectProfile',
|
||||
'subjectProfile.creator',
|
||||
'subject.did',
|
||||
)
|
||||
.select([
|
||||
'assertion.uri as uri',
|
||||
'assertion.cid as cid',
|
||||
'assertion.assertion as assertionType',
|
||||
'assertion.confirmUri',
|
||||
'assertion.confirmCid',
|
||||
'assertion.confirmCreated',
|
||||
'assertion.confirmIndexed',
|
||||
'author.did as authorDid',
|
||||
'author.handle as authorHandle',
|
||||
'author.declarationCid as authorDeclarationCid',
|
||||
'author.actorType as authorActorType',
|
||||
'authorProfile.displayName as authorDisplayName',
|
||||
'subject.did as subjectDid',
|
||||
'subject.handle as subjectHandle',
|
||||
'subject.declarationCid as subjectDeclarationCid',
|
||||
'subject.actorType as subjectActorType',
|
||||
'subjectProfile.displayName as subjectDisplayName',
|
||||
'assertion.createdAt as createdAt',
|
||||
'assertion.indexedAt as indexedAt',
|
||||
])
|
||||
|
||||
if (authorInfo) {
|
||||
assertionsReq = assertionsReq.where(
|
||||
'assertion.creator',
|
||||
'=',
|
||||
authorInfo.did,
|
||||
)
|
||||
}
|
||||
|
||||
if (subjectInfo) {
|
||||
assertionsReq = assertionsReq.where(
|
||||
'assertion.subjectDid',
|
||||
'=',
|
||||
subjectInfo.did,
|
||||
)
|
||||
}
|
||||
|
||||
assertionsReq = paginate(assertionsReq, {
|
||||
limit,
|
||||
before,
|
||||
by: ref('assertion.createdAt'),
|
||||
})
|
||||
|
||||
const assertionsRes = await assertionsReq.execute()
|
||||
const assertions = assertionsRes.map((row) => ({
|
||||
uri: row.uri,
|
||||
cid: row.cid,
|
||||
assertion: row.assertionType,
|
||||
confirmation:
|
||||
row.confirmUri &&
|
||||
row.confirmCid &&
|
||||
row.confirmIndexed &&
|
||||
row.confirmCreated
|
||||
? {
|
||||
uri: row.confirmUri,
|
||||
cid: row.confirmCid,
|
||||
indexedAt: row.confirmIndexed,
|
||||
createdAt: row.confirmCreated,
|
||||
}
|
||||
: undefined,
|
||||
author: {
|
||||
did: row.authorDid,
|
||||
handle: row.authorHandle,
|
||||
declaration: {
|
||||
cid: row.authorDeclarationCid,
|
||||
actorType: row.authorActorType,
|
||||
},
|
||||
displayName: row.authorDisplayName || undefined,
|
||||
},
|
||||
subject: {
|
||||
did: row.subjectDid,
|
||||
handle: row.subjectHandle,
|
||||
declaration: {
|
||||
cid: row.subjectDeclarationCid,
|
||||
actorType: row.subjectActorType,
|
||||
},
|
||||
displayName: row.subjectDisplayName || undefined,
|
||||
},
|
||||
createdAt: row.createdAt,
|
||||
indexedAt: row.indexedAt,
|
||||
}))
|
||||
|
||||
return {
|
||||
encoding: 'application/json',
|
||||
body: {
|
||||
assertions,
|
||||
cursor: assertions.at(-1)?.createdAt,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
@ -12,6 +12,7 @@ import getFollowers from './graph/getFollowers'
|
||||
import getFollows from './graph/getFollows'
|
||||
import getMembers from './graph/getMembers'
|
||||
import getMemberships from './graph/getMemberships'
|
||||
import getAssertions from './graph/getAssertions'
|
||||
import getUsersSearch from './actor/search'
|
||||
import getUsersTypeahead from './actor/searchTypeahead'
|
||||
import getNotifications from './notification/list'
|
||||
@ -33,6 +34,7 @@ export default function (server: Server) {
|
||||
getFollows(server)
|
||||
getMembers(server)
|
||||
getMemberships(server)
|
||||
getAssertions(server)
|
||||
getUsersSearch(server)
|
||||
getUsersTypeahead(server)
|
||||
getNotifications(server)
|
||||
|
@ -40,6 +40,7 @@ import * as AppBskyFeedGetRepostedBy from './types/app/bsky/feed/getRepostedBy'
|
||||
import * as AppBskyFeedGetTimeline from './types/app/bsky/feed/getTimeline'
|
||||
import * as AppBskyFeedGetVotes from './types/app/bsky/feed/getVotes'
|
||||
import * as AppBskyFeedSetVote from './types/app/bsky/feed/setVote'
|
||||
import * as AppBskyGraphGetAssertions from './types/app/bsky/graph/getAssertions'
|
||||
import * as AppBskyGraphGetFollowers from './types/app/bsky/graph/getFollowers'
|
||||
import * as AppBskyGraphGetFollows from './types/app/bsky/graph/getFollows'
|
||||
import * as AppBskyGraphGetMembers from './types/app/bsky/graph/getMembers'
|
||||
@ -371,6 +372,11 @@ export class GraphNS {
|
||||
this._server = server
|
||||
}
|
||||
|
||||
getAssertions(handler: AppBskyGraphGetAssertions.Handler) {
|
||||
const schema = 'app.bsky.graph.getAssertions' // @ts-ignore
|
||||
return this._server.xrpc.method(schema, handler)
|
||||
}
|
||||
|
||||
getFollowers(handler: AppBskyGraphGetFollowers.Handler) {
|
||||
const schema = 'app.bsky.graph.getFollowers' // @ts-ignore
|
||||
return this._server.xrpc.method(schema, handler)
|
||||
|
@ -3005,6 +3005,231 @@ export const methodSchemaDict: Record<string, MethodSchema> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
'app.bsky.graph.getAssertions': {
|
||||
lexicon: 1,
|
||||
id: 'app.bsky.graph.getAssertions',
|
||||
type: 'query',
|
||||
description: 'General-purpose query for assertions.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
author: {
|
||||
type: 'string',
|
||||
},
|
||||
subject: {
|
||||
type: 'string',
|
||||
},
|
||||
assertion: {
|
||||
type: 'string',
|
||||
},
|
||||
confirmed: {
|
||||
type: 'boolean',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
maximum: 100,
|
||||
},
|
||||
before: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
encoding: 'application/json',
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['assertions'],
|
||||
properties: {
|
||||
cursor: {
|
||||
type: 'string',
|
||||
},
|
||||
assertions: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
required: [
|
||||
'uri',
|
||||
'cid',
|
||||
'assertion',
|
||||
'author',
|
||||
'subject',
|
||||
'indexedAt',
|
||||
'createdAt',
|
||||
],
|
||||
properties: {
|
||||
uri: {
|
||||
type: 'string',
|
||||
},
|
||||
cid: {
|
||||
type: 'string',
|
||||
},
|
||||
assertion: {
|
||||
type: 'string',
|
||||
},
|
||||
confirmation: {
|
||||
$ref: '#/$defs/confirmation',
|
||||
},
|
||||
author: {
|
||||
$ref: '#/$defs/actor',
|
||||
},
|
||||
subject: {
|
||||
$ref: '#/$defs/actor',
|
||||
},
|
||||
indexedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
$defs: {
|
||||
confirmation: {
|
||||
type: 'object',
|
||||
required: ['uri', 'cid', 'indexedAt', 'createdAt'],
|
||||
properties: {
|
||||
uri: {
|
||||
type: 'string',
|
||||
},
|
||||
cid: {
|
||||
type: 'string',
|
||||
},
|
||||
indexedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
},
|
||||
},
|
||||
},
|
||||
actor: {
|
||||
type: 'object',
|
||||
required: ['did', 'declaration', 'handle'],
|
||||
properties: {
|
||||
did: {
|
||||
type: 'string',
|
||||
},
|
||||
declaration: {
|
||||
$ref: '#/$defs/declaration',
|
||||
},
|
||||
handle: {
|
||||
type: 'string',
|
||||
},
|
||||
displayName: {
|
||||
type: 'string',
|
||||
maxLength: 64,
|
||||
},
|
||||
},
|
||||
},
|
||||
declaration: {
|
||||
type: 'object',
|
||||
required: ['cid', 'actorType'],
|
||||
properties: {
|
||||
cid: {
|
||||
type: 'string',
|
||||
},
|
||||
actorType: {
|
||||
oneOf: [
|
||||
{
|
||||
$ref: '#/$defs/actorKnown',
|
||||
},
|
||||
{
|
||||
$ref: '#/$defs/actorUnknown',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
actorKnown: {
|
||||
type: 'string',
|
||||
enum: ['app.bsky.system.actorUser', 'app.bsky.system.actorScene'],
|
||||
},
|
||||
actorUnknown: {
|
||||
type: 'string',
|
||||
not: {
|
||||
enum: ['app.bsky.system.actorUser', 'app.bsky.system.actorScene'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
defs: {
|
||||
confirmation: {
|
||||
type: 'object',
|
||||
required: ['uri', 'cid', 'indexedAt', 'createdAt'],
|
||||
properties: {
|
||||
uri: {
|
||||
type: 'string',
|
||||
},
|
||||
cid: {
|
||||
type: 'string',
|
||||
},
|
||||
indexedAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
},
|
||||
},
|
||||
},
|
||||
actor: {
|
||||
type: 'object',
|
||||
required: ['did', 'declaration', 'handle'],
|
||||
properties: {
|
||||
did: {
|
||||
type: 'string',
|
||||
},
|
||||
declaration: {
|
||||
$ref: '#/$defs/declaration',
|
||||
},
|
||||
handle: {
|
||||
type: 'string',
|
||||
},
|
||||
displayName: {
|
||||
type: 'string',
|
||||
maxLength: 64,
|
||||
},
|
||||
},
|
||||
},
|
||||
declaration: {
|
||||
type: 'object',
|
||||
required: ['cid', 'actorType'],
|
||||
properties: {
|
||||
cid: {
|
||||
type: 'string',
|
||||
},
|
||||
actorType: {
|
||||
oneOf: [
|
||||
{
|
||||
$ref: '#/$defs/actorKnown',
|
||||
},
|
||||
{
|
||||
$ref: '#/$defs/actorUnknown',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
actorKnown: {
|
||||
type: 'string',
|
||||
enum: ['app.bsky.system.actorUser', 'app.bsky.system.actorScene'],
|
||||
},
|
||||
actorUnknown: {
|
||||
type: 'string',
|
||||
not: {
|
||||
enum: ['app.bsky.system.actorUser', 'app.bsky.system.actorScene'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'app.bsky.graph.getFollowers': {
|
||||
lexicon: 1,
|
||||
id: 'app.bsky.graph.getFollowers',
|
||||
|
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* GENERATED CODE - DO NOT MODIFY
|
||||
*/
|
||||
import express from 'express'
|
||||
|
||||
export interface QueryParams {
|
||||
author?: string;
|
||||
subject?: string;
|
||||
assertion?: string;
|
||||
confirmed?: boolean;
|
||||
limit?: number;
|
||||
before?: string;
|
||||
}
|
||||
|
||||
export type HandlerInput = undefined
|
||||
|
||||
export interface HandlerSuccess {
|
||||
encoding: 'application/json';
|
||||
body: OutputSchema;
|
||||
}
|
||||
|
||||
export interface HandlerError {
|
||||
status: number;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export type HandlerOutput = HandlerError | HandlerSuccess
|
||||
|
||||
export type ActorKnown =
|
||||
| 'app.bsky.system.actorUser'
|
||||
| 'app.bsky.system.actorScene'
|
||||
export type ActorUnknown = string
|
||||
|
||||
export interface OutputSchema {
|
||||
cursor?: string;
|
||||
assertions: {
|
||||
uri: string,
|
||||
cid: string,
|
||||
assertion: string,
|
||||
confirmation?: Confirmation,
|
||||
author: Actor,
|
||||
subject: Actor,
|
||||
indexedAt: string,
|
||||
createdAt: string,
|
||||
}[];
|
||||
}
|
||||
export interface Confirmation {
|
||||
uri: string;
|
||||
cid: string;
|
||||
indexedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
export interface Actor {
|
||||
did: string;
|
||||
declaration: Declaration;
|
||||
handle: string;
|
||||
displayName?: string;
|
||||
}
|
||||
export interface Declaration {
|
||||
cid: string;
|
||||
actorType: ActorKnown | ActorUnknown;
|
||||
}
|
||||
|
||||
export type Handler = (
|
||||
params: QueryParams,
|
||||
input: HandlerInput,
|
||||
req: express.Request,
|
||||
res: express.Response
|
||||
) => Promise<HandlerOutput> | HandlerOutput
|
1310
packages/pds/tests/views/__snapshots__/assertions.test.ts.snap
Normal file
1310
packages/pds/tests/views/__snapshots__/assertions.test.ts.snap
Normal file
File diff suppressed because it is too large
Load Diff
207
packages/pds/tests/views/assertions.test.ts
Normal file
207
packages/pds/tests/views/assertions.test.ts
Normal file
@ -0,0 +1,207 @@
|
||||
import AtpApi, { ServiceClient as AtpServiceClient } from '@atproto/api'
|
||||
import {
|
||||
runTestServer,
|
||||
forSnapshot,
|
||||
CloseFn,
|
||||
constantDate,
|
||||
paginateAll,
|
||||
} from '../_util'
|
||||
import { SeedClient } from '../seeds/client'
|
||||
import basicSeed from '../seeds/basic'
|
||||
|
||||
describe('pds assertion views', () => {
|
||||
let client: AtpServiceClient
|
||||
let close: CloseFn
|
||||
let sc: SeedClient
|
||||
|
||||
// account dids, for convenience
|
||||
let alice: string
|
||||
const scene = 'scene.test'
|
||||
const otherScene = 'other-scene.test'
|
||||
const carolScene = 'carol-scene.test'
|
||||
|
||||
beforeAll(async () => {
|
||||
const server = await runTestServer({
|
||||
dbPostgresSchema: 'views_assertions',
|
||||
})
|
||||
close = server.close
|
||||
client = AtpApi.service(server.url)
|
||||
sc = new SeedClient(client)
|
||||
await basicSeed(sc)
|
||||
alice = sc.dids.alice
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await close()
|
||||
})
|
||||
|
||||
const getCursors = (items: { createdAt?: string }[]) =>
|
||||
items.map((item) => item.createdAt ?? constantDate)
|
||||
|
||||
const getSortedCursors = (items: { createdAt?: string }[]) =>
|
||||
getCursors(items).sort((a, b) => tstamp(b) - tstamp(a))
|
||||
|
||||
const tstamp = (x: string) => new Date(x).getTime()
|
||||
|
||||
it('requires an author or subject', async () => {
|
||||
const promise = client.app.bsky.graph.getAssertions()
|
||||
await expect(promise).rejects.toThrow('Must provide an author or subject')
|
||||
})
|
||||
|
||||
it('fetches assertions by author', async () => {
|
||||
const sceneAssertions = await client.app.bsky.graph.getAssertions({
|
||||
author: sc.scenes[scene].did,
|
||||
})
|
||||
|
||||
expect(forSnapshot(sceneAssertions.data)).toMatchSnapshot()
|
||||
expect(getCursors(sceneAssertions.data.assertions)).toEqual(
|
||||
getSortedCursors(sceneAssertions.data.assertions),
|
||||
)
|
||||
|
||||
const otherSceneAssertions = await client.app.bsky.graph.getAssertions({
|
||||
author: sc.scenes[otherScene].did,
|
||||
})
|
||||
|
||||
expect(forSnapshot(otherSceneAssertions.data)).toMatchSnapshot()
|
||||
expect(getCursors(otherSceneAssertions.data.assertions)).toEqual(
|
||||
getSortedCursors(otherSceneAssertions.data.assertions),
|
||||
)
|
||||
|
||||
const carolSceneAssertions = await client.app.bsky.graph.getAssertions({
|
||||
author: sc.scenes[carolScene].did,
|
||||
})
|
||||
|
||||
expect(forSnapshot(carolSceneAssertions.data)).toMatchSnapshot()
|
||||
expect(getCursors(carolSceneAssertions.data.assertions)).toEqual(
|
||||
getSortedCursors(carolSceneAssertions.data.assertions),
|
||||
)
|
||||
})
|
||||
|
||||
it('fetches assertions by subject', async () => {
|
||||
const aliceAssertions = await client.app.bsky.graph.getAssertions({
|
||||
subject: sc.accounts[alice].did,
|
||||
})
|
||||
|
||||
expect(forSnapshot(aliceAssertions.data)).toMatchSnapshot()
|
||||
expect(getCursors(aliceAssertions.data.assertions)).toEqual(
|
||||
getSortedCursors(aliceAssertions.data.assertions),
|
||||
)
|
||||
})
|
||||
|
||||
it('fetches assertions by author & subject', async () => {
|
||||
const aliceSceneAssertions = await client.app.bsky.graph.getAssertions({
|
||||
author: sc.scenes[scene].did,
|
||||
subject: sc.accounts[alice].did,
|
||||
})
|
||||
|
||||
expect(forSnapshot(aliceSceneAssertions.data)).toMatchSnapshot()
|
||||
expect(getCursors(aliceSceneAssertions.data.assertions)).toEqual(
|
||||
getSortedCursors(aliceSceneAssertions.data.assertions),
|
||||
)
|
||||
})
|
||||
|
||||
it('fetches assertions by author filtered by confirmation status', async () => {
|
||||
const sceneAssertionsConfirmed = await client.app.bsky.graph.getAssertions({
|
||||
author: sc.scenes[scene].did,
|
||||
confirmed: true,
|
||||
})
|
||||
|
||||
expect(forSnapshot(sceneAssertionsConfirmed.data)).toMatchSnapshot()
|
||||
expect(getCursors(sceneAssertionsConfirmed.data.assertions)).toEqual(
|
||||
getSortedCursors(sceneAssertionsConfirmed.data.assertions),
|
||||
)
|
||||
|
||||
const sceneAssertionsUnconfirmed =
|
||||
await client.app.bsky.graph.getAssertions({
|
||||
author: sc.scenes[scene].did,
|
||||
confirmed: false,
|
||||
})
|
||||
|
||||
expect(forSnapshot(sceneAssertionsUnconfirmed.data)).toMatchSnapshot()
|
||||
expect(getCursors(sceneAssertionsUnconfirmed.data.assertions)).toEqual(
|
||||
getSortedCursors(sceneAssertionsUnconfirmed.data.assertions),
|
||||
)
|
||||
|
||||
const otherSceneAssertionsConfirmed =
|
||||
await client.app.bsky.graph.getAssertions({
|
||||
author: sc.scenes[otherScene].did,
|
||||
confirmed: true,
|
||||
})
|
||||
|
||||
expect(forSnapshot(otherSceneAssertionsConfirmed.data)).toMatchSnapshot()
|
||||
expect(getCursors(otherSceneAssertionsConfirmed.data.assertions)).toEqual(
|
||||
getSortedCursors(otherSceneAssertionsConfirmed.data.assertions),
|
||||
)
|
||||
|
||||
const otherSceneAssertionsUnconfirmed =
|
||||
await client.app.bsky.graph.getAssertions({
|
||||
author: sc.scenes[otherScene].did,
|
||||
confirmed: false,
|
||||
})
|
||||
|
||||
expect(forSnapshot(otherSceneAssertionsUnconfirmed.data)).toMatchSnapshot()
|
||||
expect(getCursors(otherSceneAssertionsUnconfirmed.data.assertions)).toEqual(
|
||||
getSortedCursors(otherSceneAssertionsUnconfirmed.data.assertions),
|
||||
)
|
||||
|
||||
const carolSceneAssertionsConfirmed =
|
||||
await client.app.bsky.graph.getAssertions({
|
||||
author: sc.scenes[carolScene].did,
|
||||
confirmed: true,
|
||||
})
|
||||
|
||||
expect(forSnapshot(carolSceneAssertionsConfirmed.data)).toMatchSnapshot()
|
||||
expect(getCursors(carolSceneAssertionsConfirmed.data.assertions)).toEqual(
|
||||
getSortedCursors(carolSceneAssertionsConfirmed.data.assertions),
|
||||
)
|
||||
|
||||
const carolSceneAssertionsUnconfirmed =
|
||||
await client.app.bsky.graph.getAssertions({
|
||||
author: sc.scenes[carolScene].did,
|
||||
confirmed: true,
|
||||
})
|
||||
|
||||
expect(forSnapshot(carolSceneAssertionsUnconfirmed.data)).toMatchSnapshot()
|
||||
expect(getCursors(carolSceneAssertionsUnconfirmed.data.assertions)).toEqual(
|
||||
getSortedCursors(carolSceneAssertionsUnconfirmed.data.assertions),
|
||||
)
|
||||
})
|
||||
|
||||
it('fetches assertions by author filtered by type', async () => {
|
||||
const sceneAssertionsMember = await client.app.bsky.graph.getAssertions({
|
||||
author: sc.scenes[scene].did,
|
||||
assertion: 'app.bsky.graph.assertMember',
|
||||
})
|
||||
|
||||
expect(forSnapshot(sceneAssertionsMember.data)).toMatchSnapshot()
|
||||
expect(getCursors(sceneAssertionsMember.data.assertions)).toEqual(
|
||||
getSortedCursors(sceneAssertionsMember.data.assertions),
|
||||
)
|
||||
|
||||
const sceneAssertionsCreator = await client.app.bsky.graph.getAssertions({
|
||||
author: sc.scenes[scene].did,
|
||||
assertion: 'app.bsky.graph.assertCreator',
|
||||
})
|
||||
|
||||
expect(forSnapshot(sceneAssertionsCreator.data)).toMatchSnapshot()
|
||||
expect(getCursors(sceneAssertionsCreator.data.assertions)).toEqual(
|
||||
getSortedCursors(sceneAssertionsCreator.data.assertions),
|
||||
)
|
||||
})
|
||||
|
||||
it('paginates assertions', async () => {
|
||||
const paginator = async (cursor?: string) => {
|
||||
const res = await client.app.bsky.graph.getAssertions({
|
||||
author: sc.scenes[scene].did,
|
||||
before: cursor,
|
||||
limit: 2,
|
||||
})
|
||||
return res.data
|
||||
}
|
||||
|
||||
const paginatedAll = await paginateAll(paginator)
|
||||
paginatedAll.forEach((res) =>
|
||||
expect(res.assertions.length).toBeLessThanOrEqual(2),
|
||||
)
|
||||
})
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user