Ozone sets ()

*  Initial implementation of sets api on ozone

*  Introduce sortDirection to querySets

* 🧹 Cleanup and refactor

*  Align setView for response

* ♻️ Rename and add specific error

* 🐛 Cleanup unnecessary check that is covered by lexicon

*  Rename remove to delete and add set suffix

*  Use id and createdAt for values pagination

*  Add index on createdAt for query perf and other cleanups

* 🐛 Set createdAt when inserting values

* 📝 Add changeset

*  Add index on setId and createdAt
This commit is contained in:
Foysal Ahamed 2024-10-08 19:16:09 +02:00 committed by GitHub
parent 3e1ae8d1d5
commit 22d039a229
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 3212 additions and 1 deletions

@ -0,0 +1,6 @@
---
"@atproto/ozone": patch
"@atproto/api": patch
---
Sets api to manage lists of strings on ozone, mostly aimed for automod configuration

@ -0,0 +1,32 @@
{
"lexicon": 1,
"id": "tools.ozone.set.addValues",
"defs": {
"main": {
"type": "procedure",
"description": "Add values to a specific set. Attempting to add values to a set that does not exist will result in an error.",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["name", "values"],
"properties": {
"name": {
"type": "string",
"description": "Name of the set to add values to"
},
"values": {
"type": "array",
"minLength": 1,
"maxLength": 1000,
"items": {
"type": "string"
},
"description": "Array of string values to add to the set"
}
}
}
}
}
}
}

@ -0,0 +1,49 @@
{
"lexicon": 1,
"id": "tools.ozone.set.defs",
"defs": {
"set": {
"type": "object",
"required": ["name"],
"properties": {
"name": {
"type": "string",
"minLength": 3,
"maxLength": 128
},
"description": {
"type": "string",
"maxGraphemes": 1024,
"maxLength": 10240
}
}
},
"setView": {
"type": "object",
"required": ["name", "setSize", "createdAt", "updatedAt"],
"properties": {
"name": {
"type": "string",
"minLength": 3,
"maxLength": 128
},
"description": {
"type": "string",
"maxGraphemes": 1024,
"maxLength": 10240
},
"setSize": {
"type": "integer"
},
"createdAt": {
"type": "string",
"format": "datetime"
},
"updatedAt": {
"type": "string",
"format": "datetime"
}
}
}
}
}

@ -0,0 +1,36 @@
{
"lexicon": 1,
"id": "tools.ozone.set.deleteSet",
"defs": {
"main": {
"type": "procedure",
"description": "Delete an entire set. Attempting to delete a set that does not exist will result in an error.",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["name"],
"properties": {
"name": {
"type": "string",
"description": "Name of the set to delete"
}
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"properties": {}
}
},
"errors": [
{
"name": "SetNotFound",
"description": "set with the given name does not exist"
}
]
}
}
}

@ -0,0 +1,37 @@
{
"lexicon": 1,
"id": "tools.ozone.set.deleteValues",
"defs": {
"main": {
"type": "procedure",
"description": "Delete values from a specific set. Attempting to delete values that are not in the set will not result in an error",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["name", "values"],
"properties": {
"name": {
"type": "string",
"description": "Name of the set to delete values from"
},
"values": {
"type": "array",
"minLength": 1,
"items": {
"type": "string"
},
"description": "Array of string values to delete from the set"
}
}
}
},
"errors": [
{
"name": "SetNotFound",
"description": "set with the given name does not exist"
}
]
}
}
}

@ -0,0 +1,56 @@
{
"lexicon": 1,
"id": "tools.ozone.set.getValues",
"defs": {
"main": {
"type": "query",
"description": "Get a specific set and its values",
"parameters": {
"type": "params",
"required": ["name"],
"properties": {
"name": {
"type": "string"
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 1000,
"default": 100
},
"cursor": {
"type": "string"
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["set", "values"],
"properties": {
"set": {
"type": "ref",
"ref": "tools.ozone.set.defs#setView"
},
"values": {
"type": "array",
"items": {
"type": "string"
}
},
"cursor": {
"type": "string"
}
}
}
},
"errors": [
{
"name": "SetNotFound",
"description": "set with the given name does not exist"
}
]
}
}
}

@ -0,0 +1,57 @@
{
"lexicon": 1,
"id": "tools.ozone.set.querySets",
"defs": {
"main": {
"type": "query",
"description": "Query available sets",
"parameters": {
"type": "params",
"properties": {
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 50
},
"cursor": {
"type": "string"
},
"namePrefix": {
"type": "string"
},
"sortBy": {
"type": "string",
"enum": ["name", "createdAt", "updatedAt"],
"default": "name"
},
"sortDirection": {
"type": "string",
"default": "asc",
"enum": ["asc", "desc"],
"description": "Defaults to ascending order of name field."
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["sets"],
"properties": {
"sets": {
"type": "array",
"items": {
"type": "ref",
"ref": "tools.ozone.set.defs#setView"
}
},
"cursor": {
"type": "string"
}
}
}
}
}
}
}

@ -0,0 +1,24 @@
{
"lexicon": 1,
"id": "tools.ozone.set.upsertSet",
"defs": {
"main": {
"type": "procedure",
"description": "Create or update set metadata",
"input": {
"encoding": "application/json",
"schema": {
"type": "ref",
"ref": "tools.ozone.set.defs#set"
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "ref",
"ref": "tools.ozone.set.defs#setView"
}
}
}
}
}

@ -206,6 +206,13 @@ import * as ToolsOzoneModerationQueryEvents from './types/tools/ozone/moderation
import * as ToolsOzoneModerationQueryStatuses from './types/tools/ozone/moderation/queryStatuses'
import * as ToolsOzoneModerationSearchRepos from './types/tools/ozone/moderation/searchRepos'
import * as ToolsOzoneServerGetConfig from './types/tools/ozone/server/getConfig'
import * as ToolsOzoneSetAddValues from './types/tools/ozone/set/addValues'
import * as ToolsOzoneSetDefs from './types/tools/ozone/set/defs'
import * as ToolsOzoneSetDeleteSet from './types/tools/ozone/set/deleteSet'
import * as ToolsOzoneSetDeleteValues from './types/tools/ozone/set/deleteValues'
import * as ToolsOzoneSetGetValues from './types/tools/ozone/set/getValues'
import * as ToolsOzoneSetQuerySets from './types/tools/ozone/set/querySets'
import * as ToolsOzoneSetUpsertSet from './types/tools/ozone/set/upsertSet'
import * as ToolsOzoneSignatureDefs from './types/tools/ozone/signature/defs'
import * as ToolsOzoneSignatureFindCorrelation from './types/tools/ozone/signature/findCorrelation'
import * as ToolsOzoneSignatureFindRelatedAccounts from './types/tools/ozone/signature/findRelatedAccounts'
@ -418,6 +425,13 @@ export * as ToolsOzoneModerationQueryEvents from './types/tools/ozone/moderation
export * as ToolsOzoneModerationQueryStatuses from './types/tools/ozone/moderation/queryStatuses'
export * as ToolsOzoneModerationSearchRepos from './types/tools/ozone/moderation/searchRepos'
export * as ToolsOzoneServerGetConfig from './types/tools/ozone/server/getConfig'
export * as ToolsOzoneSetAddValues from './types/tools/ozone/set/addValues'
export * as ToolsOzoneSetDefs from './types/tools/ozone/set/defs'
export * as ToolsOzoneSetDeleteSet from './types/tools/ozone/set/deleteSet'
export * as ToolsOzoneSetDeleteValues from './types/tools/ozone/set/deleteValues'
export * as ToolsOzoneSetGetValues from './types/tools/ozone/set/getValues'
export * as ToolsOzoneSetQuerySets from './types/tools/ozone/set/querySets'
export * as ToolsOzoneSetUpsertSet from './types/tools/ozone/set/upsertSet'
export * as ToolsOzoneSignatureDefs from './types/tools/ozone/signature/defs'
export * as ToolsOzoneSignatureFindCorrelation from './types/tools/ozone/signature/findCorrelation'
export * as ToolsOzoneSignatureFindRelatedAccounts from './types/tools/ozone/signature/findRelatedAccounts'
@ -3404,6 +3418,7 @@ export class ToolsOzoneNS {
communication: ToolsOzoneCommunicationNS
moderation: ToolsOzoneModerationNS
server: ToolsOzoneServerNS
set: ToolsOzoneSetNS
signature: ToolsOzoneSignatureNS
team: ToolsOzoneTeamNS
@ -3412,6 +3427,7 @@ export class ToolsOzoneNS {
this.communication = new ToolsOzoneCommunicationNS(client)
this.moderation = new ToolsOzoneModerationNS(client)
this.server = new ToolsOzoneServerNS(client)
this.set = new ToolsOzoneSetNS(client)
this.signature = new ToolsOzoneSignatureNS(client)
this.team = new ToolsOzoneTeamNS(client)
}
@ -3604,6 +3620,73 @@ export class ToolsOzoneServerNS {
}
}
export class ToolsOzoneSetNS {
_client: XrpcClient
constructor(client: XrpcClient) {
this._client = client
}
addValues(
data?: ToolsOzoneSetAddValues.InputSchema,
opts?: ToolsOzoneSetAddValues.CallOptions,
): Promise<ToolsOzoneSetAddValues.Response> {
return this._client.call('tools.ozone.set.addValues', opts?.qp, data, opts)
}
deleteSet(
data?: ToolsOzoneSetDeleteSet.InputSchema,
opts?: ToolsOzoneSetDeleteSet.CallOptions,
): Promise<ToolsOzoneSetDeleteSet.Response> {
return this._client
.call('tools.ozone.set.deleteSet', opts?.qp, data, opts)
.catch((e) => {
throw ToolsOzoneSetDeleteSet.toKnownErr(e)
})
}
deleteValues(
data?: ToolsOzoneSetDeleteValues.InputSchema,
opts?: ToolsOzoneSetDeleteValues.CallOptions,
): Promise<ToolsOzoneSetDeleteValues.Response> {
return this._client
.call('tools.ozone.set.deleteValues', opts?.qp, data, opts)
.catch((e) => {
throw ToolsOzoneSetDeleteValues.toKnownErr(e)
})
}
getValues(
params?: ToolsOzoneSetGetValues.QueryParams,
opts?: ToolsOzoneSetGetValues.CallOptions,
): Promise<ToolsOzoneSetGetValues.Response> {
return this._client
.call('tools.ozone.set.getValues', params, undefined, opts)
.catch((e) => {
throw ToolsOzoneSetGetValues.toKnownErr(e)
})
}
querySets(
params?: ToolsOzoneSetQuerySets.QueryParams,
opts?: ToolsOzoneSetQuerySets.CallOptions,
): Promise<ToolsOzoneSetQuerySets.Response> {
return this._client.call(
'tools.ozone.set.querySets',
params,
undefined,
opts,
)
}
upsertSet(
data?: ToolsOzoneSetUpsertSet.InputSchema,
opts?: ToolsOzoneSetUpsertSet.CallOptions,
): Promise<ToolsOzoneSetUpsertSet.Response> {
return this._client.call('tools.ozone.set.upsertSet', opts?.qp, data, opts)
}
}
export class ToolsOzoneSignatureNS {
_client: XrpcClient

@ -12139,6 +12139,300 @@ export const schemaDict = {
},
},
},
ToolsOzoneSetAddValues: {
lexicon: 1,
id: 'tools.ozone.set.addValues',
defs: {
main: {
type: 'procedure',
description:
'Add values to a specific set. Attempting to add values to a set that does not exist will result in an error.',
input: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['name', 'values'],
properties: {
name: {
type: 'string',
description: 'Name of the set to add values to',
},
values: {
type: 'array',
minLength: 1,
maxLength: 1000,
items: {
type: 'string',
},
description: 'Array of string values to add to the set',
},
},
},
},
},
},
},
ToolsOzoneSetDefs: {
lexicon: 1,
id: 'tools.ozone.set.defs',
defs: {
set: {
type: 'object',
required: ['name'],
properties: {
name: {
type: 'string',
minLength: 3,
maxLength: 128,
},
description: {
type: 'string',
maxGraphemes: 1024,
maxLength: 10240,
},
},
},
setView: {
type: 'object',
required: ['name', 'setSize', 'createdAt', 'updatedAt'],
properties: {
name: {
type: 'string',
minLength: 3,
maxLength: 128,
},
description: {
type: 'string',
maxGraphemes: 1024,
maxLength: 10240,
},
setSize: {
type: 'integer',
},
createdAt: {
type: 'string',
format: 'datetime',
},
updatedAt: {
type: 'string',
format: 'datetime',
},
},
},
},
},
ToolsOzoneSetDeleteSet: {
lexicon: 1,
id: 'tools.ozone.set.deleteSet',
defs: {
main: {
type: 'procedure',
description:
'Delete an entire set. Attempting to delete a set that does not exist will result in an error.',
input: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['name'],
properties: {
name: {
type: 'string',
description: 'Name of the set to delete',
},
},
},
},
output: {
encoding: 'application/json',
schema: {
type: 'object',
properties: {},
},
},
errors: [
{
name: 'SetNotFound',
description: 'set with the given name does not exist',
},
],
},
},
},
ToolsOzoneSetDeleteValues: {
lexicon: 1,
id: 'tools.ozone.set.deleteValues',
defs: {
main: {
type: 'procedure',
description:
'Delete values from a specific set. Attempting to delete values that are not in the set will not result in an error',
input: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['name', 'values'],
properties: {
name: {
type: 'string',
description: 'Name of the set to delete values from',
},
values: {
type: 'array',
minLength: 1,
items: {
type: 'string',
},
description: 'Array of string values to delete from the set',
},
},
},
},
errors: [
{
name: 'SetNotFound',
description: 'set with the given name does not exist',
},
],
},
},
},
ToolsOzoneSetGetValues: {
lexicon: 1,
id: 'tools.ozone.set.getValues',
defs: {
main: {
type: 'query',
description: 'Get a specific set and its values',
parameters: {
type: 'params',
required: ['name'],
properties: {
name: {
type: 'string',
},
limit: {
type: 'integer',
minimum: 1,
maximum: 1000,
default: 100,
},
cursor: {
type: 'string',
},
},
},
output: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['set', 'values'],
properties: {
set: {
type: 'ref',
ref: 'lex:tools.ozone.set.defs#setView',
},
values: {
type: 'array',
items: {
type: 'string',
},
},
cursor: {
type: 'string',
},
},
},
},
errors: [
{
name: 'SetNotFound',
description: 'set with the given name does not exist',
},
],
},
},
},
ToolsOzoneSetQuerySets: {
lexicon: 1,
id: 'tools.ozone.set.querySets',
defs: {
main: {
type: 'query',
description: 'Query available sets',
parameters: {
type: 'params',
properties: {
limit: {
type: 'integer',
minimum: 1,
maximum: 100,
default: 50,
},
cursor: {
type: 'string',
},
namePrefix: {
type: 'string',
},
sortBy: {
type: 'string',
enum: ['name', 'createdAt', 'updatedAt'],
default: 'name',
},
sortDirection: {
type: 'string',
default: 'asc',
enum: ['asc', 'desc'],
description: 'Defaults to ascending order of name field.',
},
},
},
output: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['sets'],
properties: {
sets: {
type: 'array',
items: {
type: 'ref',
ref: 'lex:tools.ozone.set.defs#setView',
},
},
cursor: {
type: 'string',
},
},
},
},
},
},
},
ToolsOzoneSetUpsertSet: {
lexicon: 1,
id: 'tools.ozone.set.upsertSet',
defs: {
main: {
type: 'procedure',
description: 'Create or update set metadata',
input: {
encoding: 'application/json',
schema: {
type: 'ref',
ref: 'lex:tools.ozone.set.defs#set',
},
},
output: {
encoding: 'application/json',
schema: {
type: 'ref',
ref: 'lex:tools.ozone.set.defs#setView',
},
},
},
},
},
ToolsOzoneSignatureDefs: {
lexicon: 1,
id: 'tools.ozone.signature.defs',
@ -12764,6 +13058,13 @@ export const ids = {
ToolsOzoneModerationQueryStatuses: 'tools.ozone.moderation.queryStatuses',
ToolsOzoneModerationSearchRepos: 'tools.ozone.moderation.searchRepos',
ToolsOzoneServerGetConfig: 'tools.ozone.server.getConfig',
ToolsOzoneSetAddValues: 'tools.ozone.set.addValues',
ToolsOzoneSetDefs: 'tools.ozone.set.defs',
ToolsOzoneSetDeleteSet: 'tools.ozone.set.deleteSet',
ToolsOzoneSetDeleteValues: 'tools.ozone.set.deleteValues',
ToolsOzoneSetGetValues: 'tools.ozone.set.getValues',
ToolsOzoneSetQuerySets: 'tools.ozone.set.querySets',
ToolsOzoneSetUpsertSet: 'tools.ozone.set.upsertSet',
ToolsOzoneSignatureDefs: 'tools.ozone.signature.defs',
ToolsOzoneSignatureFindCorrelation: 'tools.ozone.signature.findCorrelation',
ToolsOzoneSignatureFindRelatedAccounts:

@ -0,0 +1,34 @@
/**
* GENERATED CODE - DO NOT MODIFY
*/
import { HeadersMap, 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 {
/** Name of the set to add values to */
name: string
/** Array of string values to add to the set */
values: string[]
[k: string]: unknown
}
export interface CallOptions {
signal?: AbortSignal
headers?: HeadersMap
qp?: QueryParams
encoding?: 'application/json'
}
export interface Response {
success: boolean
headers: HeadersMap
}
export function toKnownErr(e: any) {
return e
}

@ -0,0 +1,44 @@
/**
* GENERATED CODE - DO NOT MODIFY
*/
import { ValidationResult, BlobRef } from '@atproto/lexicon'
import { isObj, hasProp } from '../../../../util'
import { lexicons } from '../../../../lexicons'
import { CID } from 'multiformats/cid'
export interface Set {
name: string
description?: string
[k: string]: unknown
}
export function isSet(v: unknown): v is Set {
return (
isObj(v) && hasProp(v, '$type') && v.$type === 'tools.ozone.set.defs#set'
)
}
export function validateSet(v: unknown): ValidationResult {
return lexicons.validate('tools.ozone.set.defs#set', v)
}
export interface SetView {
name: string
description?: string
setSize: number
createdAt: string
updatedAt: string
[k: string]: unknown
}
export function isSetView(v: unknown): v is SetView {
return (
isObj(v) &&
hasProp(v, '$type') &&
v.$type === 'tools.ozone.set.defs#setView'
)
}
export function validateSetView(v: unknown): ValidationResult {
return lexicons.validate('tools.ozone.set.defs#setView', v)
}

@ -0,0 +1,47 @@
/**
* GENERATED CODE - DO NOT MODIFY
*/
import { HeadersMap, 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 {
/** Name of the set to delete */
name: string
[k: string]: unknown
}
export interface OutputSchema {
[k: string]: unknown
}
export interface CallOptions {
signal?: AbortSignal
headers?: HeadersMap
qp?: QueryParams
encoding?: 'application/json'
}
export interface Response {
success: boolean
headers: HeadersMap
data: OutputSchema
}
export class SetNotFoundError extends XRPCError {
constructor(src: XRPCError) {
super(src.status, src.error, src.message, src.headers, { cause: src })
}
}
export function toKnownErr(e: any) {
if (e instanceof XRPCError) {
if (e.error === 'SetNotFound') return new SetNotFoundError(e)
}
return e
}

@ -0,0 +1,44 @@
/**
* GENERATED CODE - DO NOT MODIFY
*/
import { HeadersMap, 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 {
/** Name of the set to delete values from */
name: string
/** Array of string values to delete from the set */
values: string[]
[k: string]: unknown
}
export interface CallOptions {
signal?: AbortSignal
headers?: HeadersMap
qp?: QueryParams
encoding?: 'application/json'
}
export interface Response {
success: boolean
headers: HeadersMap
}
export class SetNotFoundError extends XRPCError {
constructor(src: XRPCError) {
super(src.status, src.error, src.message, src.headers, { cause: src })
}
}
export function toKnownErr(e: any) {
if (e instanceof XRPCError) {
if (e.error === 'SetNotFound') return new SetNotFoundError(e)
}
return e
}

@ -0,0 +1,49 @@
/**
* GENERATED CODE - DO NOT MODIFY
*/
import { HeadersMap, XRPCError } from '@atproto/xrpc'
import { ValidationResult, BlobRef } from '@atproto/lexicon'
import { isObj, hasProp } from '../../../../util'
import { lexicons } from '../../../../lexicons'
import { CID } from 'multiformats/cid'
import * as ToolsOzoneSetDefs from './defs'
export interface QueryParams {
name: string
limit?: number
cursor?: string
}
export type InputSchema = undefined
export interface OutputSchema {
set: ToolsOzoneSetDefs.SetView
values: string[]
cursor?: string
[k: string]: unknown
}
export interface CallOptions {
signal?: AbortSignal
headers?: HeadersMap
}
export interface Response {
success: boolean
headers: HeadersMap
data: OutputSchema
}
export class SetNotFoundError extends XRPCError {
constructor(src: XRPCError) {
super(src.status, src.error, src.message, src.headers, { cause: src })
}
}
export function toKnownErr(e: any) {
if (e instanceof XRPCError) {
if (e.error === 'SetNotFound') return new SetNotFoundError(e)
}
return e
}

@ -0,0 +1,41 @@
/**
* GENERATED CODE - DO NOT MODIFY
*/
import { HeadersMap, XRPCError } from '@atproto/xrpc'
import { ValidationResult, BlobRef } from '@atproto/lexicon'
import { isObj, hasProp } from '../../../../util'
import { lexicons } from '../../../../lexicons'
import { CID } from 'multiformats/cid'
import * as ToolsOzoneSetDefs from './defs'
export interface QueryParams {
limit?: number
cursor?: string
namePrefix?: string
sortBy?: 'name' | 'createdAt' | 'updatedAt'
/** Defaults to ascending order of name field. */
sortDirection?: 'asc' | 'desc'
}
export type InputSchema = undefined
export interface OutputSchema {
sets: ToolsOzoneSetDefs.SetView[]
cursor?: string
[k: string]: unknown
}
export interface CallOptions {
signal?: AbortSignal
headers?: HeadersMap
}
export interface Response {
success: boolean
headers: HeadersMap
data: OutputSchema
}
export function toKnownErr(e: any) {
return e
}

@ -0,0 +1,31 @@
/**
* GENERATED CODE - DO NOT MODIFY
*/
import { HeadersMap, XRPCError } from '@atproto/xrpc'
import { ValidationResult, BlobRef } from '@atproto/lexicon'
import { isObj, hasProp } from '../../../../util'
import { lexicons } from '../../../../lexicons'
import { CID } from 'multiformats/cid'
import * as ToolsOzoneSetDefs from './defs'
export interface QueryParams {}
export type InputSchema = ToolsOzoneSetDefs.Set
export type OutputSchema = ToolsOzoneSetDefs.SetView
export interface CallOptions {
signal?: AbortSignal
headers?: HeadersMap
qp?: QueryParams
encoding?: 'application/json'
}
export interface Response {
success: boolean
headers: HeadersMap
data: OutputSchema
}
export function toKnownErr(e: any) {
return e
}

@ -23,6 +23,12 @@ import listMembers from './team/listMembers'
import getConfig from './server/getConfig'
import chat from './chat'
import proxied from './proxied'
import setAddValues from './set/addValues'
import setGetValues from './set/getValues'
import querySets from './set/querySets'
import upsertSet from './set/upsertSet'
import setDeleteValues from './set/deleteValues'
import deleteSet from './set/deleteSet'
import getRepos from './moderation/getRepos'
export * as health from './health'
@ -54,5 +60,11 @@ export default function (server: Server, ctx: AppContext) {
chat(server, ctx)
proxied(server, ctx)
getConfig(server, ctx)
setAddValues(server, ctx)
setGetValues(server, ctx)
querySets(server, ctx)
upsertSet(server, ctx)
setDeleteValues(server, ctx)
deleteSet(server, ctx)
return server
}

@ -0,0 +1,28 @@
import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
import { Server } from '../../lexicon'
import AppContext from '../../context'
export default function (server: Server, ctx: AppContext) {
server.tools.ozone.set.addValues({
auth: ctx.authVerifier.modOrAdminToken,
handler: async ({ input, auth }) => {
const access = auth.credentials
const db = ctx.db
const { name, values } = input.body
if (!access.isModerator) {
throw new AuthRequiredError(
'Must be a moderator to add values to a set',
)
}
const setService = ctx.setService(db)
const set = await setService.getByName(name)
if (!set) {
throw new InvalidRequestError(`Set with name "${name}" does not exist`)
}
await setService.addValues(set.id, values)
},
})
}

@ -0,0 +1,34 @@
import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
import { Server } from '../../lexicon'
import AppContext from '../../context'
export default function (server: Server, ctx: AppContext) {
server.tools.ozone.set.deleteSet({
auth: ctx.authVerifier.modOrAdminToken,
handler: async ({ input, auth }) => {
const access = auth.credentials
const db = ctx.db
const { name } = input.body
if (!access.isModerator) {
throw new AuthRequiredError('Must be a moderator to delete a set')
}
const setService = ctx.setService(db)
const set = await setService.getByName(name)
if (!set) {
throw new InvalidRequestError(
`Set with name "${name}" does not exist`,
'SetNotFound',
)
}
await setService.removeSet(set.id)
return {
encoding: 'application/json',
body: {},
}
},
})
}

@ -0,0 +1,31 @@
import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
import { Server } from '../../lexicon'
import AppContext from '../../context'
export default function (server: Server, ctx: AppContext) {
server.tools.ozone.set.deleteValues({
auth: ctx.authVerifier.modOrAdminToken,
handler: async ({ input, auth }) => {
const access = auth.credentials
const db = ctx.db
const { name, values } = input.body
if (!access.isModerator) {
throw new AuthRequiredError(
'Must be a moderator to remove values from a set',
)
}
const setService = ctx.setService(db)
const set = await setService.getByName(name)
if (!set) {
throw new InvalidRequestError(
`Set with name "${name}" does not exist`,
'SetNotFound',
)
}
await setService.removeValues(set.id, values)
},
})
}

@ -0,0 +1,42 @@
import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
import { Server } from '../../lexicon'
import AppContext from '../../context'
export default function (server: Server, ctx: AppContext) {
server.tools.ozone.set.getValues({
auth: ctx.authVerifier.modOrAdminToken,
handler: async ({ params, auth }) => {
const access = auth.credentials
const db = ctx.db
const { name, limit, cursor } = params
if (!access.isModerator) {
throw new AuthRequiredError('Must be a moderator to get set details')
}
const setService = ctx.setService(db)
const result = await setService.getSetWithValues({
name,
limit,
cursor,
})
if (!result) {
throw new InvalidRequestError(
`Set with name "${name}" not found`,
'SetNotFound',
)
}
return {
encoding: 'application/json',
body: {
set: setService.view(result.set),
values: result.values,
cursor: result.cursor,
},
}
},
})
}

@ -0,0 +1,36 @@
import { AuthRequiredError } from '@atproto/xrpc-server'
import { Server } from '../../lexicon'
import AppContext from '../../context'
export default function (server: Server, ctx: AppContext) {
server.tools.ozone.set.querySets({
auth: ctx.authVerifier.modOrAdminToken,
handler: async ({ params, auth }) => {
const access = auth.credentials
const db = ctx.db
const { limit, cursor, namePrefix, sortBy, sortDirection } = params
if (!access.isModerator) {
throw new AuthRequiredError('Must be a moderator to query sets')
}
const setService = ctx.setService(db)
const queryResult = await setService.query({
limit,
cursor,
namePrefix,
sortBy,
sortDirection,
})
return {
encoding: 'application/json',
body: {
sets: queryResult.sets.map((set) => setService.view(set)),
cursor: queryResult.cursor,
},
}
},
})
}

@ -0,0 +1,38 @@
import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
import { Server } from '../../lexicon'
import AppContext from '../../context'
export default function (server: Server, ctx: AppContext) {
server.tools.ozone.set.upsertSet({
auth: ctx.authVerifier.modOrAdminToken,
handler: async ({ input, auth }) => {
const access = auth.credentials
const db = ctx.db
const { name, description } = input.body
if (!access.isModerator) {
throw new AuthRequiredError(
'Must be a moderator to create or update a set',
)
}
const setService = ctx.setService(db)
await setService.upsert({
name,
description: description ?? null,
})
const setWithSize = await setService.getByNameWithSize(name)
// Unlikely to happen since we just upserted the set
if (!setWithSize) {
throw new InvalidRequestError(`Set not found`)
}
return {
encoding: 'application/json',
body: setService.view(setWithSize),
}
},
})
}

@ -26,12 +26,14 @@ import {
ParsedLabelers,
parseLabelerHeader,
} from './util'
import { SetService, SetServiceCreator } from './set/service'
export type AppContextOptions = {
db: Database
cfg: OzoneConfig
modService: ModerationServiceCreator
communicationTemplateService: CommunicationTemplateServiceCreator
setService: SetServiceCreator
teamService: TeamServiceCreator
appviewAgent: AtpAgent
pdsAgent: AtpAgent | undefined
@ -117,6 +119,7 @@ export class AppContext {
const communicationTemplateService = CommunicationTemplateService.creator()
const teamService = TeamService.creator()
const setService = SetService.creator()
const sequencer = new Sequencer(modService(db))
@ -133,6 +136,7 @@ export class AppContext {
modService,
communicationTemplateService,
teamService,
setService,
appviewAgent,
pdsAgent,
chatAgent,
@ -182,6 +186,10 @@ export class AppContext {
return this.opts.teamService
}
get setService(): SetServiceCreator {
return this.opts.setService
}
get appviewAgent(): AtpAgent {
return this.opts.appviewAgent
}

@ -0,0 +1,53 @@
import { Kysely, sql } from 'kysely'
export async function up(db: Kysely<unknown>): Promise<void> {
// Create the sets table
await db.schema
.createTable('set_detail')
.addColumn('id', 'serial', (col) => col.primaryKey())
.addColumn('name', 'varchar', (col) => col.notNull().unique())
.addColumn('description', 'varchar')
.addColumn('createdAt', 'timestamptz', (col) =>
col.defaultTo(sql`now()`).notNull(),
)
.addColumn('updatedAt', 'timestamptz', (col) =>
col.defaultTo(sql`now()`).notNull(),
)
.execute()
// Create the set values table
await db.schema
.createTable('set_value')
.addColumn('id', 'bigserial', (col) => col.primaryKey())
.addColumn('setId', 'integer', (col) =>
col.notNull().references('set_detail.id'),
)
.addColumn('value', 'varchar', (col) => col.notNull())
.addColumn('createdAt', 'timestamptz', (col) =>
col.defaultTo(sql`now()`).notNull(),
)
.execute()
// Add indexes for better performance
await db.schema
.createIndex('set_detail_name_idx')
.on('set_detail')
.column('name')
.execute()
// Create a unique constraint on setId and value
await db.schema
.alterTable('set_value')
.addUniqueConstraint('set_value_setid_value_unique', ['setId', 'value'])
.execute()
await db.schema
.createIndex('set_value_setid_created_at_idx')
.on('set_value')
.columns(['setId', 'createdAt'])
.execute()
}
export async function down(db: Kysely<unknown>): Promise<void> {
await db.schema.dropTable('set_value').execute()
await db.schema.dropTable('set_detail').execute()
}

@ -14,3 +14,4 @@ export * as _20240814T003647759Z from './20240814T003647759Z-event-created-at-in
export * as _20240903T205730722Z from './20240903T205730722Z-add-template-lang'
export * as _20240904T205730722Z from './20240904T205730722Z-add-subject-did-index'
export * as _20241001T205730722Z from './20241001T205730722Z-subject-status-review-state-index'
export * as _20241008T205730722Z from './20241008T205730722Z-sets'

@ -146,7 +146,7 @@ export class StatusKeyset extends GenericKeyset<StatusKeysetParam, Cursor> {
type TimeIdKeysetParam = {
id: number
createdAt: string
createdAt: string | Date
}
type TimeIdResult = TimeIdKeysetParam

@ -7,6 +7,7 @@ import * as blobPushEvent from './blob_push_event'
import * as label from './label'
import * as signingKey from './signing_key'
import * as communicationTemplate from './communication_template'
import * as set from './ozone_set'
import * as member from './member'
export type DatabaseSchemaType = modEvent.PartialDB &
@ -17,6 +18,7 @@ export type DatabaseSchemaType = modEvent.PartialDB &
recordPushEvent.PartialDB &
blobPushEvent.PartialDB &
communicationTemplate.PartialDB &
set.PartialDB &
member.PartialDB
export type DatabaseSchema = Kysely<DatabaseSchemaType>

@ -0,0 +1,24 @@
import { Generated, GeneratedAlways } from 'kysely'
export const ozoneSetTableName = 'set_detail'
export const ozoneSetValueTableName = 'set_value'
export interface SetDetail {
id: GeneratedAlways<number>
name: string
description: string | null
createdAt: Generated<Date>
updatedAt: Generated<Date>
}
export interface SetValue {
id: GeneratedAlways<number>
setId: number
value: string
createdAt: Generated<Date>
}
export type PartialDB = {
[ozoneSetTableName]: SetDetail
[ozoneSetValueTableName]: SetValue
}

@ -173,6 +173,12 @@ import * as ToolsOzoneModerationQueryEvents from './types/tools/ozone/moderation
import * as ToolsOzoneModerationQueryStatuses from './types/tools/ozone/moderation/queryStatuses'
import * as ToolsOzoneModerationSearchRepos from './types/tools/ozone/moderation/searchRepos'
import * as ToolsOzoneServerGetConfig from './types/tools/ozone/server/getConfig'
import * as ToolsOzoneSetAddValues from './types/tools/ozone/set/addValues'
import * as ToolsOzoneSetDeleteSet from './types/tools/ozone/set/deleteSet'
import * as ToolsOzoneSetDeleteValues from './types/tools/ozone/set/deleteValues'
import * as ToolsOzoneSetGetValues from './types/tools/ozone/set/getValues'
import * as ToolsOzoneSetQuerySets from './types/tools/ozone/set/querySets'
import * as ToolsOzoneSetUpsertSet from './types/tools/ozone/set/upsertSet'
import * as ToolsOzoneSignatureFindCorrelation from './types/tools/ozone/signature/findCorrelation'
import * as ToolsOzoneSignatureFindRelatedAccounts from './types/tools/ozone/signature/findRelatedAccounts'
import * as ToolsOzoneSignatureSearchAccounts from './types/tools/ozone/signature/searchAccounts'
@ -2164,6 +2170,7 @@ export class ToolsOzoneNS {
communication: ToolsOzoneCommunicationNS
moderation: ToolsOzoneModerationNS
server: ToolsOzoneServerNS
set: ToolsOzoneSetNS
signature: ToolsOzoneSignatureNS
team: ToolsOzoneTeamNS
@ -2172,6 +2179,7 @@ export class ToolsOzoneNS {
this.communication = new ToolsOzoneCommunicationNS(server)
this.moderation = new ToolsOzoneModerationNS(server)
this.server = new ToolsOzoneServerNS(server)
this.set = new ToolsOzoneSetNS(server)
this.signature = new ToolsOzoneSignatureNS(server)
this.team = new ToolsOzoneTeamNS(server)
}
@ -2355,6 +2363,80 @@ export class ToolsOzoneServerNS {
}
}
export class ToolsOzoneSetNS {
_server: Server
constructor(server: Server) {
this._server = server
}
addValues<AV extends AuthVerifier>(
cfg: ConfigOf<
AV,
ToolsOzoneSetAddValues.Handler<ExtractAuth<AV>>,
ToolsOzoneSetAddValues.HandlerReqCtx<ExtractAuth<AV>>
>,
) {
const nsid = 'tools.ozone.set.addValues' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
deleteSet<AV extends AuthVerifier>(
cfg: ConfigOf<
AV,
ToolsOzoneSetDeleteSet.Handler<ExtractAuth<AV>>,
ToolsOzoneSetDeleteSet.HandlerReqCtx<ExtractAuth<AV>>
>,
) {
const nsid = 'tools.ozone.set.deleteSet' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
deleteValues<AV extends AuthVerifier>(
cfg: ConfigOf<
AV,
ToolsOzoneSetDeleteValues.Handler<ExtractAuth<AV>>,
ToolsOzoneSetDeleteValues.HandlerReqCtx<ExtractAuth<AV>>
>,
) {
const nsid = 'tools.ozone.set.deleteValues' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
getValues<AV extends AuthVerifier>(
cfg: ConfigOf<
AV,
ToolsOzoneSetGetValues.Handler<ExtractAuth<AV>>,
ToolsOzoneSetGetValues.HandlerReqCtx<ExtractAuth<AV>>
>,
) {
const nsid = 'tools.ozone.set.getValues' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
querySets<AV extends AuthVerifier>(
cfg: ConfigOf<
AV,
ToolsOzoneSetQuerySets.Handler<ExtractAuth<AV>>,
ToolsOzoneSetQuerySets.HandlerReqCtx<ExtractAuth<AV>>
>,
) {
const nsid = 'tools.ozone.set.querySets' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
upsertSet<AV extends AuthVerifier>(
cfg: ConfigOf<
AV,
ToolsOzoneSetUpsertSet.Handler<ExtractAuth<AV>>,
ToolsOzoneSetUpsertSet.HandlerReqCtx<ExtractAuth<AV>>
>,
) {
const nsid = 'tools.ozone.set.upsertSet' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
}
export class ToolsOzoneSignatureNS {
_server: Server

@ -12139,6 +12139,300 @@ export const schemaDict = {
},
},
},
ToolsOzoneSetAddValues: {
lexicon: 1,
id: 'tools.ozone.set.addValues',
defs: {
main: {
type: 'procedure',
description:
'Add values to a specific set. Attempting to add values to a set that does not exist will result in an error.',
input: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['name', 'values'],
properties: {
name: {
type: 'string',
description: 'Name of the set to add values to',
},
values: {
type: 'array',
minLength: 1,
maxLength: 1000,
items: {
type: 'string',
},
description: 'Array of string values to add to the set',
},
},
},
},
},
},
},
ToolsOzoneSetDefs: {
lexicon: 1,
id: 'tools.ozone.set.defs',
defs: {
set: {
type: 'object',
required: ['name'],
properties: {
name: {
type: 'string',
minLength: 3,
maxLength: 128,
},
description: {
type: 'string',
maxGraphemes: 1024,
maxLength: 10240,
},
},
},
setView: {
type: 'object',
required: ['name', 'setSize', 'createdAt', 'updatedAt'],
properties: {
name: {
type: 'string',
minLength: 3,
maxLength: 128,
},
description: {
type: 'string',
maxGraphemes: 1024,
maxLength: 10240,
},
setSize: {
type: 'integer',
},
createdAt: {
type: 'string',
format: 'datetime',
},
updatedAt: {
type: 'string',
format: 'datetime',
},
},
},
},
},
ToolsOzoneSetDeleteSet: {
lexicon: 1,
id: 'tools.ozone.set.deleteSet',
defs: {
main: {
type: 'procedure',
description:
'Delete an entire set. Attempting to delete a set that does not exist will result in an error.',
input: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['name'],
properties: {
name: {
type: 'string',
description: 'Name of the set to delete',
},
},
},
},
output: {
encoding: 'application/json',
schema: {
type: 'object',
properties: {},
},
},
errors: [
{
name: 'SetNotFound',
description: 'set with the given name does not exist',
},
],
},
},
},
ToolsOzoneSetDeleteValues: {
lexicon: 1,
id: 'tools.ozone.set.deleteValues',
defs: {
main: {
type: 'procedure',
description:
'Delete values from a specific set. Attempting to delete values that are not in the set will not result in an error',
input: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['name', 'values'],
properties: {
name: {
type: 'string',
description: 'Name of the set to delete values from',
},
values: {
type: 'array',
minLength: 1,
items: {
type: 'string',
},
description: 'Array of string values to delete from the set',
},
},
},
},
errors: [
{
name: 'SetNotFound',
description: 'set with the given name does not exist',
},
],
},
},
},
ToolsOzoneSetGetValues: {
lexicon: 1,
id: 'tools.ozone.set.getValues',
defs: {
main: {
type: 'query',
description: 'Get a specific set and its values',
parameters: {
type: 'params',
required: ['name'],
properties: {
name: {
type: 'string',
},
limit: {
type: 'integer',
minimum: 1,
maximum: 1000,
default: 100,
},
cursor: {
type: 'string',
},
},
},
output: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['set', 'values'],
properties: {
set: {
type: 'ref',
ref: 'lex:tools.ozone.set.defs#setView',
},
values: {
type: 'array',
items: {
type: 'string',
},
},
cursor: {
type: 'string',
},
},
},
},
errors: [
{
name: 'SetNotFound',
description: 'set with the given name does not exist',
},
],
},
},
},
ToolsOzoneSetQuerySets: {
lexicon: 1,
id: 'tools.ozone.set.querySets',
defs: {
main: {
type: 'query',
description: 'Query available sets',
parameters: {
type: 'params',
properties: {
limit: {
type: 'integer',
minimum: 1,
maximum: 100,
default: 50,
},
cursor: {
type: 'string',
},
namePrefix: {
type: 'string',
},
sortBy: {
type: 'string',
enum: ['name', 'createdAt', 'updatedAt'],
default: 'name',
},
sortDirection: {
type: 'string',
default: 'asc',
enum: ['asc', 'desc'],
description: 'Defaults to ascending order of name field.',
},
},
},
output: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['sets'],
properties: {
sets: {
type: 'array',
items: {
type: 'ref',
ref: 'lex:tools.ozone.set.defs#setView',
},
},
cursor: {
type: 'string',
},
},
},
},
},
},
},
ToolsOzoneSetUpsertSet: {
lexicon: 1,
id: 'tools.ozone.set.upsertSet',
defs: {
main: {
type: 'procedure',
description: 'Create or update set metadata',
input: {
encoding: 'application/json',
schema: {
type: 'ref',
ref: 'lex:tools.ozone.set.defs#set',
},
},
output: {
encoding: 'application/json',
schema: {
type: 'ref',
ref: 'lex:tools.ozone.set.defs#setView',
},
},
},
},
},
ToolsOzoneSignatureDefs: {
lexicon: 1,
id: 'tools.ozone.signature.defs',
@ -12764,6 +13058,13 @@ export const ids = {
ToolsOzoneModerationQueryStatuses: 'tools.ozone.moderation.queryStatuses',
ToolsOzoneModerationSearchRepos: 'tools.ozone.moderation.searchRepos',
ToolsOzoneServerGetConfig: 'tools.ozone.server.getConfig',
ToolsOzoneSetAddValues: 'tools.ozone.set.addValues',
ToolsOzoneSetDefs: 'tools.ozone.set.defs',
ToolsOzoneSetDeleteSet: 'tools.ozone.set.deleteSet',
ToolsOzoneSetDeleteValues: 'tools.ozone.set.deleteValues',
ToolsOzoneSetGetValues: 'tools.ozone.set.getValues',
ToolsOzoneSetQuerySets: 'tools.ozone.set.querySets',
ToolsOzoneSetUpsertSet: 'tools.ozone.set.upsertSet',
ToolsOzoneSignatureDefs: 'tools.ozone.signature.defs',
ToolsOzoneSignatureFindCorrelation: 'tools.ozone.signature.findCorrelation',
ToolsOzoneSignatureFindRelatedAccounts:

@ -0,0 +1,41 @@
/**
* 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 {
/** Name of the set to add values to */
name: string
/** Array of string values to add to the set */
values: 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<HA extends HandlerAuth = never> = {
auth: HA
params: QueryParams
input: HandlerInput
req: express.Request
res: express.Response
}
export type Handler<HA extends HandlerAuth = never> = (
ctx: HandlerReqCtx<HA>,
) => Promise<HandlerOutput> | HandlerOutput

@ -0,0 +1,44 @@
/**
* GENERATED CODE - DO NOT MODIFY
*/
import { ValidationResult, BlobRef } from '@atproto/lexicon'
import { lexicons } from '../../../../lexicons'
import { isObj, hasProp } from '../../../../util'
import { CID } from 'multiformats/cid'
export interface Set {
name: string
description?: string
[k: string]: unknown
}
export function isSet(v: unknown): v is Set {
return (
isObj(v) && hasProp(v, '$type') && v.$type === 'tools.ozone.set.defs#set'
)
}
export function validateSet(v: unknown): ValidationResult {
return lexicons.validate('tools.ozone.set.defs#set', v)
}
export interface SetView {
name: string
description?: string
setSize: number
createdAt: string
updatedAt: string
[k: string]: unknown
}
export function isSetView(v: unknown): v is SetView {
return (
isObj(v) &&
hasProp(v, '$type') &&
v.$type === 'tools.ozone.set.defs#setView'
)
}
export function validateSetView(v: unknown): ValidationResult {
return lexicons.validate('tools.ozone.set.defs#setView', v)
}

@ -0,0 +1,50 @@
/**
* 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 {
/** Name of the set to delete */
name: string
[k: string]: unknown
}
export interface OutputSchema {
[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?: 'SetNotFound'
}
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
auth: HA
params: QueryParams
input: HandlerInput
req: express.Request
res: express.Response
}
export type Handler<HA extends HandlerAuth = never> = (
ctx: HandlerReqCtx<HA>,
) => Promise<HandlerOutput> | HandlerOutput

@ -0,0 +1,42 @@
/**
* 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 {
/** Name of the set to delete values from */
name: string
/** Array of string values to delete from the set */
values: string[]
[k: string]: unknown
}
export interface HandlerInput {
encoding: 'application/json'
body: InputSchema
}
export interface HandlerError {
status: number
message?: string
error?: 'SetNotFound'
}
export type HandlerOutput = HandlerError | void
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
auth: HA
params: QueryParams
input: HandlerInput
req: express.Request
res: express.Response
}
export type Handler<HA extends HandlerAuth = never> = (
ctx: HandlerReqCtx<HA>,
) => Promise<HandlerOutput> | HandlerOutput

@ -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'
import * as ToolsOzoneSetDefs from './defs'
export interface QueryParams {
name: string
limit: number
cursor?: string
}
export type InputSchema = undefined
export interface OutputSchema {
set: ToolsOzoneSetDefs.SetView
values: string[]
cursor?: 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
error?: 'SetNotFound'
}
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
auth: HA
params: QueryParams
input: HandlerInput
req: express.Request
res: express.Response
}
export type Handler<HA extends HandlerAuth = never> = (
ctx: HandlerReqCtx<HA>,
) => Promise<HandlerOutput> | HandlerOutput

@ -0,0 +1,52 @@
/**
* 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'
import * as ToolsOzoneSetDefs from './defs'
export interface QueryParams {
limit: number
cursor?: string
namePrefix?: string
sortBy: 'name' | 'createdAt' | 'updatedAt'
/** Defaults to ascending order of name field. */
sortDirection: 'asc' | 'desc'
}
export type InputSchema = undefined
export interface OutputSchema {
sets: ToolsOzoneSetDefs.SetView[]
cursor?: 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<HA extends HandlerAuth = never> = {
auth: HA
params: QueryParams
input: HandlerInput
req: express.Request
res: express.Response
}
export type Handler<HA extends HandlerAuth = never> = (
ctx: HandlerReqCtx<HA>,
) => Promise<HandlerOutput> | HandlerOutput

@ -0,0 +1,43 @@
/**
* 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'
import * as ToolsOzoneSetDefs from './defs'
export interface QueryParams {}
export type InputSchema = ToolsOzoneSetDefs.Set
export type OutputSchema = ToolsOzoneSetDefs.SetView
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<HA extends HandlerAuth = never> = {
auth: HA
params: QueryParams
input: HandlerInput
req: express.Request
res: express.Response
}
export type Handler<HA extends HandlerAuth = never> = (
ctx: HandlerReqCtx<HA>,
) => Promise<HandlerOutput> | HandlerOutput

@ -0,0 +1,227 @@
import Database from '../db'
import { Selectable } from 'kysely'
import { SetDetail } from '../db/schema/ozone_set'
import { SetView } from '../lexicon/types/tools/ozone/set/defs'
import { paginate, TimeIdKeyset } from '../db/pagination'
export type SetServiceCreator = (db: Database) => SetService
export class SetService {
constructor(public db: Database) {}
static creator() {
return (db: Database) => new SetService(db)
}
buildQueryForSetWithSize() {
return this.db.db.selectFrom('set_detail as s').select([
's.id',
's.name',
's.description',
's.createdAt',
's.updatedAt',
(eb) =>
eb
.selectFrom('set_value')
.select((e) => e.fn.count<number>('setId').as('count'))
.whereRef('setId', '=', 's.id')
.as('setSize'),
])
}
async query({
limit,
cursor,
namePrefix,
sortBy,
sortDirection,
}: {
limit: number
cursor?: string
namePrefix?: string
sortBy: 'name' | 'createdAt' | 'updatedAt'
sortDirection: 'asc' | 'desc'
}): Promise<{
sets: Selectable<SetDetail & { setSize: number }>[]
cursor?: string
}> {
let qb = this.buildQueryForSetWithSize().limit(limit)
if (namePrefix) {
qb = qb.where('s.name', 'like', `${namePrefix}%`)
}
if (cursor) {
if (sortBy === 'name') {
qb = qb.where('s.name', sortDirection === 'asc' ? '>' : '<', cursor)
} else {
qb = qb.where(
`s.${sortBy}`,
sortDirection === 'asc' ? '>' : '<',
new Date(cursor),
)
}
}
qb = qb.orderBy(`s.${sortBy}`, sortDirection)
const sets = await qb.execute()
const lastItem = sets.at(-1)
return {
sets,
cursor: lastItem
? sortBy === 'name'
? lastItem?.name
: lastItem?.[sortBy].toISOString()
: undefined,
}
}
async getByName(name: string): Promise<Selectable<SetDetail> | undefined> {
const query = this.db.db
.selectFrom('set_detail')
.selectAll()
.where('name', '=', name)
return await query.executeTakeFirst()
}
async getByNameWithSize(
name: string,
): Promise<Selectable<SetDetail & { setSize: number }> | undefined> {
return await this.buildQueryForSetWithSize()
.where('s.name', '=', name)
.executeTakeFirst()
}
async getSetWithValues({
name,
limit,
cursor,
}: {
name: string
limit: number
cursor?: string
}): Promise<
| {
set: Selectable<SetDetail & { setSize: number }>
values: string[]
cursor?: string
}
| undefined
> {
const set = await this.getByNameWithSize(name)
if (!set) return undefined
const { ref } = this.db.db.dynamic
const qb = this.db.db
.selectFrom('set_value')
.selectAll()
.where('setId', '=', set.id)
const keyset = new TimeIdKeyset(ref(`createdAt`), ref('id'))
const paginatedBuilder = paginate(qb, {
limit,
cursor,
keyset,
direction: 'asc',
})
const result = await paginatedBuilder.execute()
return {
set,
values: result.map((v) => v.value),
cursor: keyset.packFromResult(result),
}
}
async upsert({
name,
description,
}: Pick<SetDetail, 'name' | 'description'>): Promise<void> {
await this.db.db
.insertInto('set_detail')
.values({
name,
description,
updatedAt: new Date(),
})
.onConflict((oc) => {
// if description is provided as a string, even an empty one, update it
// otherwise, just update the updatedAt timestamp
return oc.column('name').doUpdateSet(
typeof description === 'string'
? {
description,
updatedAt: new Date(),
}
: { updatedAt: new Date() },
)
})
.execute()
}
async addValues(setId: number, values: string[]): Promise<void> {
await this.db.transaction(async (txn) => {
const now = new Date()
const query = txn.db
.insertInto('set_value')
.values(
values.map((value) => ({
setId,
value,
createdAt: now,
})),
)
.onConflict((oc) => oc.columns(['setId', 'value']).doNothing())
await query.execute()
// Update the set's updatedAt timestamp
await txn.db
.updateTable('set_detail')
.set({ updatedAt: now })
.where('id', '=', setId)
.execute()
})
}
async removeValues(setId: number, values: string[]): Promise<void> {
if (values.length < 1) {
return
}
await this.db.transaction(async (txn) => {
const query = txn.db
.deleteFrom('set_value')
.where('setId', '=', setId)
.where('value', 'in', values)
await query.execute()
// Update the set's updatedAt timestamp
await txn.db
.updateTable('set_detail')
.set({ updatedAt: new Date() })
.where('id', '=', setId)
.execute()
})
}
async removeSet(setId: number): Promise<void> {
await this.db.transaction(async (txn) => {
await txn.db.deleteFrom('set_value').where('setId', '=', setId).execute()
await txn.db.deleteFrom('set_detail').where('id', '=', setId).execute()
})
}
view(set: Selectable<SetDetail> & { setSize: number }): SetView {
return {
name: set.name,
description: set.description || undefined,
setSize: set.setSize,
createdAt: set.createdAt.toISOString(),
updatedAt: set.updatedAt.toISOString(),
}
}
}

@ -0,0 +1,46 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ozone-sets querySets returns all sets when no parameters are provided 1`] = `
Array [
Object {
"createdAt": "1970-01-01T00:00:00.000Z",
"description": "Another test set",
"name": "another-set",
"setSize": 0,
"updatedAt": "1970-01-01T00:00:00.000Z",
},
Object {
"createdAt": "1970-01-01T00:00:00.000Z",
"description": "Test set 1",
"name": "test-set-1",
"setSize": 0,
"updatedAt": "1970-01-01T00:00:00.000Z",
},
Object {
"createdAt": "1970-01-01T00:00:00.000Z",
"name": "test-set-2",
"setSize": 0,
"updatedAt": "1970-01-01T00:00:00.000Z",
},
]
`;
exports[`ozone-sets upsertSet creates a new set 1`] = `
Object {
"createdAt": "1970-01-01T00:00:00.000Z",
"description": "A new test set",
"name": "new-test-set",
"setSize": 0,
"updatedAt": "1970-01-01T00:00:00.000Z",
}
`;
exports[`ozone-sets upsertSet updates an existing set 1`] = `
Object {
"createdAt": "1970-01-01T00:00:00.000Z",
"description": "Updated description",
"name": "new-test-set",
"setSize": 0,
"updatedAt": "1970-01-01T00:00:00.000Z",
}
`;

@ -0,0 +1,246 @@
import { TestNetwork, SeedClient, basicSeed } from '@atproto/dev-env'
import AtpAgent, {
ToolsOzoneSetDefs,
ToolsOzoneSetQuerySets,
} from '@atproto/api'
import { forSnapshot } from './_util'
import { ids } from '../src/lexicon/lexicons'
describe('ozone-sets', () => {
let network: TestNetwork
let agent: AtpAgent
let sc: SeedClient
const sampleSet1 = {
name: 'test-set-1',
description: 'Test set 1',
}
const sampleSet2 = {
name: 'test-set-2',
}
const sampleSet3 = {
name: 'another-set',
description: 'Another test set',
}
const upsertSet = async (set: ToolsOzoneSetDefs.Set) => {
const { data } = await agent.tools.ozone.set.upsertSet(set, {
encoding: 'application/json',
headers: await network.ozone.modHeaders(
ids.ToolsOzoneSetUpsertSet,
'admin',
),
})
return data
}
const removeSet = async (name: string) => {
await agent.tools.ozone.set.deleteSet(
{ name },
{
encoding: 'application/json',
headers: await network.ozone.modHeaders(
ids.ToolsOzoneSetDeleteSet,
'admin',
),
},
)
}
const addValues = async (name: string, values: string[]) => {
await agent.tools.ozone.set.addValues(
{ name, values },
{
encoding: 'application/json',
headers: await network.ozone.modHeaders(
ids.ToolsOzoneSetAddValues,
'admin',
),
},
)
}
const getValues = async (name: string, limit?: number, cursor?: string) => {
const { data } = await agent.tools.ozone.set.getValues(
{ name, limit, cursor },
{
headers: await network.ozone.modHeaders(
ids.ToolsOzoneSetGetValues,
'moderator',
),
},
)
return data
}
const querySets = async (params: ToolsOzoneSetQuerySets.QueryParams) => {
const { data } = await agent.tools.ozone.set.querySets(params, {
headers: await network.ozone.modHeaders(
ids.ToolsOzoneSetQuerySets,
'moderator',
),
})
return data
}
beforeAll(async () => {
network = await TestNetwork.create({
dbPostgresSchema: 'ozone_sets',
})
agent = network.ozone.getClient()
sc = network.getSeedClient()
await basicSeed(sc)
await network.processAll()
})
afterAll(async () => {
await network.close()
})
describe('querySets', () => {
beforeAll(async () => {
await Promise.all([
upsertSet(sampleSet1),
upsertSet(sampleSet2),
upsertSet(sampleSet3),
])
})
afterAll(async () => {
await Promise.all([
removeSet(sampleSet1.name),
removeSet(sampleSet2.name),
removeSet(sampleSet3.name),
])
})
it('returns all sets when no parameters are provided', async () => {
const result = await querySets({})
expect(result.sets.length).toBe(3)
expect(forSnapshot(result.sets)).toMatchSnapshot()
})
it('limits the number of returned sets', async () => {
const result = await querySets({ limit: 2 })
expect(result.sets.length).toBe(2)
expect(result.cursor).toBeDefined()
})
it('returns sets after the cursor', async () => {
const firstPage = await querySets({ limit: 2 })
const secondPage = await querySets({ cursor: firstPage.cursor })
expect(secondPage.sets.length).toBe(1)
expect(secondPage.sets[0].name).toBe('test-set-2')
})
it('filters sets by name prefix', async () => {
const result = await querySets({ namePrefix: 'test-' })
expect(result.sets.length).toBe(2)
expect(result.sets.map((s) => s.name)).toEqual([
'test-set-1',
'test-set-2',
])
})
it('sorts sets by given column and direction', async () => {
const sortedByName = await querySets({ sortBy: 'name' })
expect(sortedByName.sets.map((s) => s.name)).toEqual([
'another-set',
'test-set-1',
'test-set-2',
])
const reverseSortedByName = await querySets({
sortBy: 'name',
sortDirection: 'desc',
})
expect(reverseSortedByName.sets.map((s) => s.name)).toEqual([
'test-set-2',
'test-set-1',
'another-set',
])
})
})
describe('upsertSet', () => {
afterAll(async () => {
await removeSet('new-test-set')
})
it('creates a new set', async () => {
const result = await upsertSet({
name: 'new-test-set',
description: 'A new test set',
})
expect(forSnapshot(result)).toMatchSnapshot()
})
it('updates an existing set', async () => {
const result = await upsertSet({
name: 'new-test-set',
description: 'Updated description',
})
expect(forSnapshot(result)).toMatchSnapshot()
})
it('allows setting empty description', async () => {
const result = await upsertSet({
name: 'new-test-set',
description: '',
})
expect(result.description).toBeUndefined()
})
})
describe('addValues', () => {
beforeAll(async () => {
await upsertSet(sampleSet1)
await upsertSet(sampleSet2)
})
afterAll(async () => {
await removeSet(sampleSet1.name)
await removeSet(sampleSet2.name)
})
it('adds new values to an existing set', async () => {
const newValues = ['value1', 'value2', 'value3']
await addValues(sampleSet1.name, newValues)
const result = await getValues(sampleSet1.name)
expect(result.values).toEqual(expect.arrayContaining(newValues))
})
it('does not duplicate existing values', async () => {
const initialValues = ['initial1', 'initial2']
await addValues(sampleSet2.name, initialValues)
const newValues = ['initial2', 'new1', 'new2']
await addValues(sampleSet2.name, newValues)
const result = await getValues(sampleSet2.name)
expect(result.values).toEqual(
expect.arrayContaining([...initialValues, 'new1', 'new2']),
)
expect(result.values.filter((v) => v === 'initial2').length).toBe(1)
})
})
describe('getValues', () => {
beforeAll(async () => {
await upsertSet(sampleSet1)
})
afterAll(async () => {
await removeSet(sampleSet1.name)
})
it('paginates values from a set', async () => {
const allValues = Array.from({ length: 9 }, (_, i) => `value${i}`)
await addValues(sampleSet1.name, allValues)
const firstPage = await getValues(sampleSet1.name, 3)
const secondPage = await getValues(sampleSet1.name, 3, firstPage.cursor)
const lastPage = await getValues(sampleSet1.name, 3, secondPage.cursor)
expect(firstPage.values).toEqual(allValues.slice(0, 3))
expect(secondPage.values).toEqual(allValues.slice(3, 6))
expect(lastPage.values).toEqual(allValues.slice(6, 9))
})
})
})

@ -173,6 +173,12 @@ import * as ToolsOzoneModerationQueryEvents from './types/tools/ozone/moderation
import * as ToolsOzoneModerationQueryStatuses from './types/tools/ozone/moderation/queryStatuses'
import * as ToolsOzoneModerationSearchRepos from './types/tools/ozone/moderation/searchRepos'
import * as ToolsOzoneServerGetConfig from './types/tools/ozone/server/getConfig'
import * as ToolsOzoneSetAddValues from './types/tools/ozone/set/addValues'
import * as ToolsOzoneSetDeleteSet from './types/tools/ozone/set/deleteSet'
import * as ToolsOzoneSetDeleteValues from './types/tools/ozone/set/deleteValues'
import * as ToolsOzoneSetGetValues from './types/tools/ozone/set/getValues'
import * as ToolsOzoneSetQuerySets from './types/tools/ozone/set/querySets'
import * as ToolsOzoneSetUpsertSet from './types/tools/ozone/set/upsertSet'
import * as ToolsOzoneSignatureFindCorrelation from './types/tools/ozone/signature/findCorrelation'
import * as ToolsOzoneSignatureFindRelatedAccounts from './types/tools/ozone/signature/findRelatedAccounts'
import * as ToolsOzoneSignatureSearchAccounts from './types/tools/ozone/signature/searchAccounts'
@ -2164,6 +2170,7 @@ export class ToolsOzoneNS {
communication: ToolsOzoneCommunicationNS
moderation: ToolsOzoneModerationNS
server: ToolsOzoneServerNS
set: ToolsOzoneSetNS
signature: ToolsOzoneSignatureNS
team: ToolsOzoneTeamNS
@ -2172,6 +2179,7 @@ export class ToolsOzoneNS {
this.communication = new ToolsOzoneCommunicationNS(server)
this.moderation = new ToolsOzoneModerationNS(server)
this.server = new ToolsOzoneServerNS(server)
this.set = new ToolsOzoneSetNS(server)
this.signature = new ToolsOzoneSignatureNS(server)
this.team = new ToolsOzoneTeamNS(server)
}
@ -2355,6 +2363,80 @@ export class ToolsOzoneServerNS {
}
}
export class ToolsOzoneSetNS {
_server: Server
constructor(server: Server) {
this._server = server
}
addValues<AV extends AuthVerifier>(
cfg: ConfigOf<
AV,
ToolsOzoneSetAddValues.Handler<ExtractAuth<AV>>,
ToolsOzoneSetAddValues.HandlerReqCtx<ExtractAuth<AV>>
>,
) {
const nsid = 'tools.ozone.set.addValues' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
deleteSet<AV extends AuthVerifier>(
cfg: ConfigOf<
AV,
ToolsOzoneSetDeleteSet.Handler<ExtractAuth<AV>>,
ToolsOzoneSetDeleteSet.HandlerReqCtx<ExtractAuth<AV>>
>,
) {
const nsid = 'tools.ozone.set.deleteSet' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
deleteValues<AV extends AuthVerifier>(
cfg: ConfigOf<
AV,
ToolsOzoneSetDeleteValues.Handler<ExtractAuth<AV>>,
ToolsOzoneSetDeleteValues.HandlerReqCtx<ExtractAuth<AV>>
>,
) {
const nsid = 'tools.ozone.set.deleteValues' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
getValues<AV extends AuthVerifier>(
cfg: ConfigOf<
AV,
ToolsOzoneSetGetValues.Handler<ExtractAuth<AV>>,
ToolsOzoneSetGetValues.HandlerReqCtx<ExtractAuth<AV>>
>,
) {
const nsid = 'tools.ozone.set.getValues' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
querySets<AV extends AuthVerifier>(
cfg: ConfigOf<
AV,
ToolsOzoneSetQuerySets.Handler<ExtractAuth<AV>>,
ToolsOzoneSetQuerySets.HandlerReqCtx<ExtractAuth<AV>>
>,
) {
const nsid = 'tools.ozone.set.querySets' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
upsertSet<AV extends AuthVerifier>(
cfg: ConfigOf<
AV,
ToolsOzoneSetUpsertSet.Handler<ExtractAuth<AV>>,
ToolsOzoneSetUpsertSet.HandlerReqCtx<ExtractAuth<AV>>
>,
) {
const nsid = 'tools.ozone.set.upsertSet' // @ts-ignore
return this._server.xrpc.method(nsid, cfg)
}
}
export class ToolsOzoneSignatureNS {
_server: Server

@ -12139,6 +12139,300 @@ export const schemaDict = {
},
},
},
ToolsOzoneSetAddValues: {
lexicon: 1,
id: 'tools.ozone.set.addValues',
defs: {
main: {
type: 'procedure',
description:
'Add values to a specific set. Attempting to add values to a set that does not exist will result in an error.',
input: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['name', 'values'],
properties: {
name: {
type: 'string',
description: 'Name of the set to add values to',
},
values: {
type: 'array',
minLength: 1,
maxLength: 1000,
items: {
type: 'string',
},
description: 'Array of string values to add to the set',
},
},
},
},
},
},
},
ToolsOzoneSetDefs: {
lexicon: 1,
id: 'tools.ozone.set.defs',
defs: {
set: {
type: 'object',
required: ['name'],
properties: {
name: {
type: 'string',
minLength: 3,
maxLength: 128,
},
description: {
type: 'string',
maxGraphemes: 1024,
maxLength: 10240,
},
},
},
setView: {
type: 'object',
required: ['name', 'setSize', 'createdAt', 'updatedAt'],
properties: {
name: {
type: 'string',
minLength: 3,
maxLength: 128,
},
description: {
type: 'string',
maxGraphemes: 1024,
maxLength: 10240,
},
setSize: {
type: 'integer',
},
createdAt: {
type: 'string',
format: 'datetime',
},
updatedAt: {
type: 'string',
format: 'datetime',
},
},
},
},
},
ToolsOzoneSetDeleteSet: {
lexicon: 1,
id: 'tools.ozone.set.deleteSet',
defs: {
main: {
type: 'procedure',
description:
'Delete an entire set. Attempting to delete a set that does not exist will result in an error.',
input: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['name'],
properties: {
name: {
type: 'string',
description: 'Name of the set to delete',
},
},
},
},
output: {
encoding: 'application/json',
schema: {
type: 'object',
properties: {},
},
},
errors: [
{
name: 'SetNotFound',
description: 'set with the given name does not exist',
},
],
},
},
},
ToolsOzoneSetDeleteValues: {
lexicon: 1,
id: 'tools.ozone.set.deleteValues',
defs: {
main: {
type: 'procedure',
description:
'Delete values from a specific set. Attempting to delete values that are not in the set will not result in an error',
input: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['name', 'values'],
properties: {
name: {
type: 'string',
description: 'Name of the set to delete values from',
},
values: {
type: 'array',
minLength: 1,
items: {
type: 'string',
},
description: 'Array of string values to delete from the set',
},
},
},
},
errors: [
{
name: 'SetNotFound',
description: 'set with the given name does not exist',
},
],
},
},
},
ToolsOzoneSetGetValues: {
lexicon: 1,
id: 'tools.ozone.set.getValues',
defs: {
main: {
type: 'query',
description: 'Get a specific set and its values',
parameters: {
type: 'params',
required: ['name'],
properties: {
name: {
type: 'string',
},
limit: {
type: 'integer',
minimum: 1,
maximum: 1000,
default: 100,
},
cursor: {
type: 'string',
},
},
},
output: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['set', 'values'],
properties: {
set: {
type: 'ref',
ref: 'lex:tools.ozone.set.defs#setView',
},
values: {
type: 'array',
items: {
type: 'string',
},
},
cursor: {
type: 'string',
},
},
},
},
errors: [
{
name: 'SetNotFound',
description: 'set with the given name does not exist',
},
],
},
},
},
ToolsOzoneSetQuerySets: {
lexicon: 1,
id: 'tools.ozone.set.querySets',
defs: {
main: {
type: 'query',
description: 'Query available sets',
parameters: {
type: 'params',
properties: {
limit: {
type: 'integer',
minimum: 1,
maximum: 100,
default: 50,
},
cursor: {
type: 'string',
},
namePrefix: {
type: 'string',
},
sortBy: {
type: 'string',
enum: ['name', 'createdAt', 'updatedAt'],
default: 'name',
},
sortDirection: {
type: 'string',
default: 'asc',
enum: ['asc', 'desc'],
description: 'Defaults to ascending order of name field.',
},
},
},
output: {
encoding: 'application/json',
schema: {
type: 'object',
required: ['sets'],
properties: {
sets: {
type: 'array',
items: {
type: 'ref',
ref: 'lex:tools.ozone.set.defs#setView',
},
},
cursor: {
type: 'string',
},
},
},
},
},
},
},
ToolsOzoneSetUpsertSet: {
lexicon: 1,
id: 'tools.ozone.set.upsertSet',
defs: {
main: {
type: 'procedure',
description: 'Create or update set metadata',
input: {
encoding: 'application/json',
schema: {
type: 'ref',
ref: 'lex:tools.ozone.set.defs#set',
},
},
output: {
encoding: 'application/json',
schema: {
type: 'ref',
ref: 'lex:tools.ozone.set.defs#setView',
},
},
},
},
},
ToolsOzoneSignatureDefs: {
lexicon: 1,
id: 'tools.ozone.signature.defs',
@ -12764,6 +13058,13 @@ export const ids = {
ToolsOzoneModerationQueryStatuses: 'tools.ozone.moderation.queryStatuses',
ToolsOzoneModerationSearchRepos: 'tools.ozone.moderation.searchRepos',
ToolsOzoneServerGetConfig: 'tools.ozone.server.getConfig',
ToolsOzoneSetAddValues: 'tools.ozone.set.addValues',
ToolsOzoneSetDefs: 'tools.ozone.set.defs',
ToolsOzoneSetDeleteSet: 'tools.ozone.set.deleteSet',
ToolsOzoneSetDeleteValues: 'tools.ozone.set.deleteValues',
ToolsOzoneSetGetValues: 'tools.ozone.set.getValues',
ToolsOzoneSetQuerySets: 'tools.ozone.set.querySets',
ToolsOzoneSetUpsertSet: 'tools.ozone.set.upsertSet',
ToolsOzoneSignatureDefs: 'tools.ozone.signature.defs',
ToolsOzoneSignatureFindCorrelation: 'tools.ozone.signature.findCorrelation',
ToolsOzoneSignatureFindRelatedAccounts:

@ -0,0 +1,41 @@
/**
* 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 {
/** Name of the set to add values to */
name: string
/** Array of string values to add to the set */
values: 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<HA extends HandlerAuth = never> = {
auth: HA
params: QueryParams
input: HandlerInput
req: express.Request
res: express.Response
}
export type Handler<HA extends HandlerAuth = never> = (
ctx: HandlerReqCtx<HA>,
) => Promise<HandlerOutput> | HandlerOutput

@ -0,0 +1,44 @@
/**
* GENERATED CODE - DO NOT MODIFY
*/
import { ValidationResult, BlobRef } from '@atproto/lexicon'
import { lexicons } from '../../../../lexicons'
import { isObj, hasProp } from '../../../../util'
import { CID } from 'multiformats/cid'
export interface Set {
name: string
description?: string
[k: string]: unknown
}
export function isSet(v: unknown): v is Set {
return (
isObj(v) && hasProp(v, '$type') && v.$type === 'tools.ozone.set.defs#set'
)
}
export function validateSet(v: unknown): ValidationResult {
return lexicons.validate('tools.ozone.set.defs#set', v)
}
export interface SetView {
name: string
description?: string
setSize: number
createdAt: string
updatedAt: string
[k: string]: unknown
}
export function isSetView(v: unknown): v is SetView {
return (
isObj(v) &&
hasProp(v, '$type') &&
v.$type === 'tools.ozone.set.defs#setView'
)
}
export function validateSetView(v: unknown): ValidationResult {
return lexicons.validate('tools.ozone.set.defs#setView', v)
}

@ -0,0 +1,50 @@
/**
* 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 {
/** Name of the set to delete */
name: string
[k: string]: unknown
}
export interface OutputSchema {
[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?: 'SetNotFound'
}
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
auth: HA
params: QueryParams
input: HandlerInput
req: express.Request
res: express.Response
}
export type Handler<HA extends HandlerAuth = never> = (
ctx: HandlerReqCtx<HA>,
) => Promise<HandlerOutput> | HandlerOutput

@ -0,0 +1,42 @@
/**
* 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 {
/** Name of the set to delete values from */
name: string
/** Array of string values to delete from the set */
values: string[]
[k: string]: unknown
}
export interface HandlerInput {
encoding: 'application/json'
body: InputSchema
}
export interface HandlerError {
status: number
message?: string
error?: 'SetNotFound'
}
export type HandlerOutput = HandlerError | void
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
auth: HA
params: QueryParams
input: HandlerInput
req: express.Request
res: express.Response
}
export type Handler<HA extends HandlerAuth = never> = (
ctx: HandlerReqCtx<HA>,
) => Promise<HandlerOutput> | HandlerOutput

@ -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'
import * as ToolsOzoneSetDefs from './defs'
export interface QueryParams {
name: string
limit: number
cursor?: string
}
export type InputSchema = undefined
export interface OutputSchema {
set: ToolsOzoneSetDefs.SetView
values: string[]
cursor?: 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
error?: 'SetNotFound'
}
export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
export type HandlerReqCtx<HA extends HandlerAuth = never> = {
auth: HA
params: QueryParams
input: HandlerInput
req: express.Request
res: express.Response
}
export type Handler<HA extends HandlerAuth = never> = (
ctx: HandlerReqCtx<HA>,
) => Promise<HandlerOutput> | HandlerOutput

@ -0,0 +1,52 @@
/**
* 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'
import * as ToolsOzoneSetDefs from './defs'
export interface QueryParams {
limit: number
cursor?: string
namePrefix?: string
sortBy: 'name' | 'createdAt' | 'updatedAt'
/** Defaults to ascending order of name field. */
sortDirection: 'asc' | 'desc'
}
export type InputSchema = undefined
export interface OutputSchema {
sets: ToolsOzoneSetDefs.SetView[]
cursor?: 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<HA extends HandlerAuth = never> = {
auth: HA
params: QueryParams
input: HandlerInput
req: express.Request
res: express.Response
}
export type Handler<HA extends HandlerAuth = never> = (
ctx: HandlerReqCtx<HA>,
) => Promise<HandlerOutput> | HandlerOutput

@ -0,0 +1,43 @@
/**
* 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'
import * as ToolsOzoneSetDefs from './defs'
export interface QueryParams {}
export type InputSchema = ToolsOzoneSetDefs.Set
export type OutputSchema = ToolsOzoneSetDefs.SetView
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<HA extends HandlerAuth = never> = {
auth: HA
params: QueryParams
input: HandlerInput
req: express.Request
res: express.Response
}
export type Handler<HA extends HandlerAuth = never> = (
ctx: HandlerReqCtx<HA>,
) => Promise<HandlerOutput> | HandlerOutput