Add account preferences APIs (#1013)
* Add lexicons for account preferences * Move prefs to app.bsky, codegen * Setup model and services for user prefs * Setup xrpc methods for prefs * Test preferences, fixes * Tidy * Tidy --------- Co-authored-by: Devin Ivy <devinivy@gmail.com>
This commit is contained in:
parent
5fd5c869ea
commit
df6ed7d5c0
lexicons/app/bsky/actor
packages
api/src/client
bsky/src/lexicon
pds
src
api/app/bsky
db
lexicon
services/account
tests
@ -86,6 +86,31 @@
|
||||
"following": {"type": "string", "format": "at-uri"},
|
||||
"followedBy": {"type": "string", "format": "at-uri"}
|
||||
}
|
||||
},
|
||||
"preferences": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "union",
|
||||
"refs": [
|
||||
"#adultContentPref",
|
||||
"#contentLabelPref"
|
||||
]
|
||||
}
|
||||
},
|
||||
"adultContentPref": {
|
||||
"type": "object",
|
||||
"required": ["enabled"],
|
||||
"properties": {
|
||||
"enabled": {"type": "boolean", "default": false}
|
||||
}
|
||||
},
|
||||
"contentLabelPref": {
|
||||
"type": "object",
|
||||
"required": ["label", "visibility"],
|
||||
"properties": {
|
||||
"label": {"type": "string"},
|
||||
"visibility": {"type": "string", "knownValues": ["show", "warn", "hide"]}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
28
lexicons/app/bsky/actor/getPreferences.json
Normal file
28
lexicons/app/bsky/actor/getPreferences.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"lexicon": 1,
|
||||
"id": "app.bsky.actor.getPreferences",
|
||||
"defs": {
|
||||
"main": {
|
||||
"type": "query",
|
||||
"description": "Get private preferences attached to the account.",
|
||||
"parameters": {
|
||||
"type": "params",
|
||||
"properties": {
|
||||
}
|
||||
},
|
||||
"output": {
|
||||
"encoding": "application/json",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["preferences"],
|
||||
"properties": {
|
||||
"preferences": {
|
||||
"type": "ref",
|
||||
"ref": "app.bsky.actor.defs#preferences"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
lexicons/app/bsky/actor/putPreferences.json
Normal file
23
lexicons/app/bsky/actor/putPreferences.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"lexicon": 1,
|
||||
"id": "app.bsky.actor.putPreferences",
|
||||
"defs": {
|
||||
"main": {
|
||||
"type": "procedure",
|
||||
"description": "Sets the private preferences attached to the account.",
|
||||
"input": {
|
||||
"encoding": "application/json",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["preferences"],
|
||||
"properties": {
|
||||
"preferences": {
|
||||
"type": "ref",
|
||||
"ref": "app.bsky.actor.defs#preferences"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -71,10 +71,12 @@ import * as ComAtprotoSyncNotifyOfUpdate from './types/com/atproto/sync/notifyOf
|
||||
import * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCrawl'
|
||||
import * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos'
|
||||
import * as AppBskyActorDefs from './types/app/bsky/actor/defs'
|
||||
import * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences'
|
||||
import * as AppBskyActorGetProfile from './types/app/bsky/actor/getProfile'
|
||||
import * as AppBskyActorGetProfiles from './types/app/bsky/actor/getProfiles'
|
||||
import * as AppBskyActorGetSuggestions from './types/app/bsky/actor/getSuggestions'
|
||||
import * as AppBskyActorProfile from './types/app/bsky/actor/profile'
|
||||
import * as AppBskyActorPutPreferences from './types/app/bsky/actor/putPreferences'
|
||||
import * as AppBskyActorSearchActors from './types/app/bsky/actor/searchActors'
|
||||
import * as AppBskyActorSearchActorsTypeahead from './types/app/bsky/actor/searchActorsTypeahead'
|
||||
import * as AppBskyEmbedExternal from './types/app/bsky/embed/external'
|
||||
@ -177,10 +179,12 @@ export * as ComAtprotoSyncNotifyOfUpdate from './types/com/atproto/sync/notifyOf
|
||||
export * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCrawl'
|
||||
export * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos'
|
||||
export * as AppBskyActorDefs from './types/app/bsky/actor/defs'
|
||||
export * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences'
|
||||
export * as AppBskyActorGetProfile from './types/app/bsky/actor/getProfile'
|
||||
export * as AppBskyActorGetProfiles from './types/app/bsky/actor/getProfiles'
|
||||
export * as AppBskyActorGetSuggestions from './types/app/bsky/actor/getSuggestions'
|
||||
export * as AppBskyActorProfile from './types/app/bsky/actor/profile'
|
||||
export * as AppBskyActorPutPreferences from './types/app/bsky/actor/putPreferences'
|
||||
export * as AppBskyActorSearchActors from './types/app/bsky/actor/searchActors'
|
||||
export * as AppBskyActorSearchActorsTypeahead from './types/app/bsky/actor/searchActorsTypeahead'
|
||||
export * as AppBskyEmbedExternal from './types/app/bsky/embed/external'
|
||||
@ -1012,6 +1016,17 @@ export class ActorNS {
|
||||
this.profile = new ProfileRecord(service)
|
||||
}
|
||||
|
||||
getPreferences(
|
||||
params?: AppBskyActorGetPreferences.QueryParams,
|
||||
opts?: AppBskyActorGetPreferences.CallOptions,
|
||||
): Promise<AppBskyActorGetPreferences.Response> {
|
||||
return this._service.xrpc
|
||||
.call('app.bsky.actor.getPreferences', params, undefined, opts)
|
||||
.catch((e) => {
|
||||
throw AppBskyActorGetPreferences.toKnownErr(e)
|
||||
})
|
||||
}
|
||||
|
||||
getProfile(
|
||||
params?: AppBskyActorGetProfile.QueryParams,
|
||||
opts?: AppBskyActorGetProfile.CallOptions,
|
||||
@ -1045,6 +1060,17 @@ export class ActorNS {
|
||||
})
|
||||
}
|
||||
|
||||
putPreferences(
|
||||
data?: AppBskyActorPutPreferences.InputSchema,
|
||||
opts?: AppBskyActorPutPreferences.CallOptions,
|
||||
): Promise<AppBskyActorPutPreferences.Response> {
|
||||
return this._service.xrpc
|
||||
.call('app.bsky.actor.putPreferences', opts?.qp, data, opts)
|
||||
.catch((e) => {
|
||||
throw AppBskyActorPutPreferences.toKnownErr(e)
|
||||
})
|
||||
}
|
||||
|
||||
searchActors(
|
||||
params?: AppBskyActorSearchActors.QueryParams,
|
||||
opts?: AppBskyActorSearchActors.CallOptions,
|
||||
|
@ -3509,6 +3509,66 @@ export const schemaDict = {
|
||||
},
|
||||
},
|
||||
},
|
||||
preferences: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'union',
|
||||
refs: [
|
||||
'lex:app.bsky.actor.defs#adultContentPref',
|
||||
'lex:app.bsky.actor.defs#contentLabelPref',
|
||||
],
|
||||
},
|
||||
},
|
||||
adultContentPref: {
|
||||
type: 'object',
|
||||
required: ['enabled'],
|
||||
properties: {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
contentLabelPref: {
|
||||
type: 'object',
|
||||
required: ['label', 'visibility'],
|
||||
properties: {
|
||||
label: {
|
||||
type: 'string',
|
||||
},
|
||||
visibility: {
|
||||
type: 'string',
|
||||
knownValues: ['show', 'warn', 'hide'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
AppBskyActorGetPreferences: {
|
||||
lexicon: 1,
|
||||
id: 'app.bsky.actor.getPreferences',
|
||||
defs: {
|
||||
main: {
|
||||
type: 'query',
|
||||
description: 'Get private preferences attached to the account.',
|
||||
parameters: {
|
||||
type: 'params',
|
||||
properties: {},
|
||||
},
|
||||
output: {
|
||||
encoding: 'application/json',
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['preferences'],
|
||||
properties: {
|
||||
preferences: {
|
||||
type: 'ref',
|
||||
ref: 'lex:app.bsky.actor.defs#preferences',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
AppBskyActorGetProfile: {
|
||||
@ -3655,6 +3715,29 @@ export const schemaDict = {
|
||||
},
|
||||
},
|
||||
},
|
||||
AppBskyActorPutPreferences: {
|
||||
lexicon: 1,
|
||||
id: 'app.bsky.actor.putPreferences',
|
||||
defs: {
|
||||
main: {
|
||||
type: 'procedure',
|
||||
description: 'Sets the private preferences attached to the account.',
|
||||
input: {
|
||||
encoding: 'application/json',
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['preferences'],
|
||||
properties: {
|
||||
preferences: {
|
||||
type: 'ref',
|
||||
ref: 'lex:app.bsky.actor.defs#preferences',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
AppBskyActorSearchActors: {
|
||||
lexicon: 1,
|
||||
id: 'app.bsky.actor.searchActors',
|
||||
@ -5659,10 +5742,12 @@ export const ids = {
|
||||
ComAtprotoSyncRequestCrawl: 'com.atproto.sync.requestCrawl',
|
||||
ComAtprotoSyncSubscribeRepos: 'com.atproto.sync.subscribeRepos',
|
||||
AppBskyActorDefs: 'app.bsky.actor.defs',
|
||||
AppBskyActorGetPreferences: 'app.bsky.actor.getPreferences',
|
||||
AppBskyActorGetProfile: 'app.bsky.actor.getProfile',
|
||||
AppBskyActorGetProfiles: 'app.bsky.actor.getProfiles',
|
||||
AppBskyActorGetSuggestions: 'app.bsky.actor.getSuggestions',
|
||||
AppBskyActorProfile: 'app.bsky.actor.profile',
|
||||
AppBskyActorPutPreferences: 'app.bsky.actor.putPreferences',
|
||||
AppBskyActorSearchActors: 'app.bsky.actor.searchActors',
|
||||
AppBskyActorSearchActorsTypeahead: 'app.bsky.actor.searchActorsTypeahead',
|
||||
AppBskyEmbedExternal: 'app.bsky.embed.external',
|
||||
|
@ -103,3 +103,44 @@ export function isViewerState(v: unknown): v is ViewerState {
|
||||
export function validateViewerState(v: unknown): ValidationResult {
|
||||
return lexicons.validate('app.bsky.actor.defs#viewerState', v)
|
||||
}
|
||||
|
||||
export type Preferences = (
|
||||
| AdultContentPref
|
||||
| ContentLabelPref
|
||||
| { $type: string; [k: string]: unknown }
|
||||
)[]
|
||||
|
||||
export interface AdultContentPref {
|
||||
enabled: boolean
|
||||
[k: string]: unknown
|
||||
}
|
||||
|
||||
export function isAdultContentPref(v: unknown): v is AdultContentPref {
|
||||
return (
|
||||
isObj(v) &&
|
||||
hasProp(v, '$type') &&
|
||||
v.$type === 'app.bsky.actor.defs#adultContentPref'
|
||||
)
|
||||
}
|
||||
|
||||
export function validateAdultContentPref(v: unknown): ValidationResult {
|
||||
return lexicons.validate('app.bsky.actor.defs#adultContentPref', v)
|
||||
}
|
||||
|
||||
export interface ContentLabelPref {
|
||||
label: string
|
||||
visibility: 'show' | 'warn' | 'hide' | (string & {})
|
||||
[k: string]: unknown
|
||||
}
|
||||
|
||||
export function isContentLabelPref(v: unknown): v is ContentLabelPref {
|
||||
return (
|
||||
isObj(v) &&
|
||||
hasProp(v, '$type') &&
|
||||
v.$type === 'app.bsky.actor.defs#contentLabelPref'
|
||||
)
|
||||
}
|
||||
|
||||
export function validateContentLabelPref(v: unknown): ValidationResult {
|
||||
return lexicons.validate('app.bsky.actor.defs#contentLabelPref', v)
|
||||
}
|
||||
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* GENERATED CODE - DO NOT MODIFY
|
||||
*/
|
||||
import { Headers, XRPCError } from '@atproto/xrpc'
|
||||
import { ValidationResult, BlobRef } from '@atproto/lexicon'
|
||||
import { isObj, hasProp } from '../../../../util'
|
||||
import { lexicons } from '../../../../lexicons'
|
||||
import { CID } from 'multiformats/cid'
|
||||
import * as AppBskyActorDefs from './defs'
|
||||
|
||||
export interface QueryParams {}
|
||||
|
||||
export type InputSchema = undefined
|
||||
|
||||
export interface OutputSchema {
|
||||
preferences: AppBskyActorDefs.Preferences
|
||||
[k: string]: unknown
|
||||
}
|
||||
|
||||
export interface CallOptions {
|
||||
headers?: Headers
|
||||
}
|
||||
|
||||
export interface Response {
|
||||
success: boolean
|
||||
headers: Headers
|
||||
data: OutputSchema
|
||||
}
|
||||
|
||||
export function toKnownErr(e: any) {
|
||||
if (e instanceof XRPCError) {
|
||||
}
|
||||
return e
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* GENERATED CODE - DO NOT MODIFY
|
||||
*/
|
||||
import { Headers, XRPCError } from '@atproto/xrpc'
|
||||
import { ValidationResult, BlobRef } from '@atproto/lexicon'
|
||||
import { isObj, hasProp } from '../../../../util'
|
||||
import { lexicons } from '../../../../lexicons'
|
||||
import { CID } from 'multiformats/cid'
|
||||
import * as AppBskyActorDefs from './defs'
|
||||
|
||||
export interface QueryParams {}
|
||||
|
||||
export interface InputSchema {
|
||||
preferences: AppBskyActorDefs.Preferences
|
||||
[k: string]: unknown
|
||||
}
|
||||
|
||||
export interface CallOptions {
|
||||
headers?: Headers
|
||||
qp?: QueryParams
|
||||
encoding: 'application/json'
|
||||
}
|
||||
|
||||
export interface Response {
|
||||
success: boolean
|
||||
headers: Headers
|
||||
}
|
||||
|
||||
export function toKnownErr(e: any) {
|
||||
if (e instanceof XRPCError) {
|
||||
}
|
||||
return e
|
||||
}
|
@ -64,9 +64,11 @@ import * as ComAtprotoSyncListRepos from './types/com/atproto/sync/listRepos'
|
||||
import * as ComAtprotoSyncNotifyOfUpdate from './types/com/atproto/sync/notifyOfUpdate'
|
||||
import * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCrawl'
|
||||
import * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos'
|
||||
import * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences'
|
||||
import * as AppBskyActorGetProfile from './types/app/bsky/actor/getProfile'
|
||||
import * as AppBskyActorGetProfiles from './types/app/bsky/actor/getProfiles'
|
||||
import * as AppBskyActorGetSuggestions from './types/app/bsky/actor/getSuggestions'
|
||||
import * as AppBskyActorPutPreferences from './types/app/bsky/actor/putPreferences'
|
||||
import * as AppBskyActorSearchActors from './types/app/bsky/actor/searchActors'
|
||||
import * as AppBskyActorSearchActorsTypeahead from './types/app/bsky/actor/searchActorsTypeahead'
|
||||
import * as AppBskyFeedGetAuthorFeed from './types/app/bsky/feed/getAuthorFeed'
|
||||
@ -686,6 +688,13 @@ export class ActorNS {
|
||||
this._server = server
|
||||
}
|
||||
|
||||
getPreferences<AV extends AuthVerifier>(
|
||||
cfg: ConfigOf<AV, AppBskyActorGetPreferences.Handler<ExtractAuth<AV>>>,
|
||||
) {
|
||||
const nsid = 'app.bsky.actor.getPreferences' // @ts-ignore
|
||||
return this._server.xrpc.method(nsid, cfg)
|
||||
}
|
||||
|
||||
getProfile<AV extends AuthVerifier>(
|
||||
cfg: ConfigOf<AV, AppBskyActorGetProfile.Handler<ExtractAuth<AV>>>,
|
||||
) {
|
||||
@ -707,6 +716,13 @@ export class ActorNS {
|
||||
return this._server.xrpc.method(nsid, cfg)
|
||||
}
|
||||
|
||||
putPreferences<AV extends AuthVerifier>(
|
||||
cfg: ConfigOf<AV, AppBskyActorPutPreferences.Handler<ExtractAuth<AV>>>,
|
||||
) {
|
||||
const nsid = 'app.bsky.actor.putPreferences' // @ts-ignore
|
||||
return this._server.xrpc.method(nsid, cfg)
|
||||
}
|
||||
|
||||
searchActors<AV extends AuthVerifier>(
|
||||
cfg: ConfigOf<AV, AppBskyActorSearchActors.Handler<ExtractAuth<AV>>>,
|
||||
) {
|
||||
|
@ -3417,6 +3417,66 @@ export const schemaDict = {
|
||||
},
|
||||
},
|
||||
},
|
||||
preferences: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'union',
|
||||
refs: [
|
||||
'lex:app.bsky.actor.defs#adultContentPref',
|
||||
'lex:app.bsky.actor.defs#contentLabelPref',
|
||||
],
|
||||
},
|
||||
},
|
||||
adultContentPref: {
|
||||
type: 'object',
|
||||
required: ['enabled'],
|
||||
properties: {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
contentLabelPref: {
|
||||
type: 'object',
|
||||
required: ['label', 'visibility'],
|
||||
properties: {
|
||||
label: {
|
||||
type: 'string',
|
||||
},
|
||||
visibility: {
|
||||
type: 'string',
|
||||
knownValues: ['show', 'warn', 'hide'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
AppBskyActorGetPreferences: {
|
||||
lexicon: 1,
|
||||
id: 'app.bsky.actor.getPreferences',
|
||||
defs: {
|
||||
main: {
|
||||
type: 'query',
|
||||
description: 'Get private preferences attached to the account.',
|
||||
parameters: {
|
||||
type: 'params',
|
||||
properties: {},
|
||||
},
|
||||
output: {
|
||||
encoding: 'application/json',
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['preferences'],
|
||||
properties: {
|
||||
preferences: {
|
||||
type: 'ref',
|
||||
ref: 'lex:app.bsky.actor.defs#preferences',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
AppBskyActorGetProfile: {
|
||||
@ -3563,6 +3623,29 @@ export const schemaDict = {
|
||||
},
|
||||
},
|
||||
},
|
||||
AppBskyActorPutPreferences: {
|
||||
lexicon: 1,
|
||||
id: 'app.bsky.actor.putPreferences',
|
||||
defs: {
|
||||
main: {
|
||||
type: 'procedure',
|
||||
description: 'Sets the private preferences attached to the account.',
|
||||
input: {
|
||||
encoding: 'application/json',
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['preferences'],
|
||||
properties: {
|
||||
preferences: {
|
||||
type: 'ref',
|
||||
ref: 'lex:app.bsky.actor.defs#preferences',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
AppBskyActorSearchActors: {
|
||||
lexicon: 1,
|
||||
id: 'app.bsky.actor.searchActors',
|
||||
@ -5189,10 +5272,12 @@ export const ids = {
|
||||
ComAtprotoSyncRequestCrawl: 'com.atproto.sync.requestCrawl',
|
||||
ComAtprotoSyncSubscribeRepos: 'com.atproto.sync.subscribeRepos',
|
||||
AppBskyActorDefs: 'app.bsky.actor.defs',
|
||||
AppBskyActorGetPreferences: 'app.bsky.actor.getPreferences',
|
||||
AppBskyActorGetProfile: 'app.bsky.actor.getProfile',
|
||||
AppBskyActorGetProfiles: 'app.bsky.actor.getProfiles',
|
||||
AppBskyActorGetSuggestions: 'app.bsky.actor.getSuggestions',
|
||||
AppBskyActorProfile: 'app.bsky.actor.profile',
|
||||
AppBskyActorPutPreferences: 'app.bsky.actor.putPreferences',
|
||||
AppBskyActorSearchActors: 'app.bsky.actor.searchActors',
|
||||
AppBskyActorSearchActorsTypeahead: 'app.bsky.actor.searchActorsTypeahead',
|
||||
AppBskyEmbedExternal: 'app.bsky.embed.external',
|
||||
|
@ -101,3 +101,44 @@ export function isViewerState(v: unknown): v is ViewerState {
|
||||
export function validateViewerState(v: unknown): ValidationResult {
|
||||
return lexicons.validate('app.bsky.actor.defs#viewerState', v)
|
||||
}
|
||||
|
||||
export type Preferences = (
|
||||
| AdultContentPref
|
||||
| ContentLabelPref
|
||||
| { $type: string; [k: string]: unknown }
|
||||
)[]
|
||||
|
||||
export interface AdultContentPref {
|
||||
enabled: boolean
|
||||
[k: string]: unknown
|
||||
}
|
||||
|
||||
export function isAdultContentPref(v: unknown): v is AdultContentPref {
|
||||
return (
|
||||
isObj(v) &&
|
||||
hasProp(v, '$type') &&
|
||||
v.$type === 'app.bsky.actor.defs#adultContentPref'
|
||||
)
|
||||
}
|
||||
|
||||
export function validateAdultContentPref(v: unknown): ValidationResult {
|
||||
return lexicons.validate('app.bsky.actor.defs#adultContentPref', v)
|
||||
}
|
||||
|
||||
export interface ContentLabelPref {
|
||||
label: string
|
||||
visibility: 'show' | 'warn' | 'hide' | (string & {})
|
||||
[k: string]: unknown
|
||||
}
|
||||
|
||||
export function isContentLabelPref(v: unknown): v is ContentLabelPref {
|
||||
return (
|
||||
isObj(v) &&
|
||||
hasProp(v, '$type') &&
|
||||
v.$type === 'app.bsky.actor.defs#contentLabelPref'
|
||||
)
|
||||
}
|
||||
|
||||
export function validateContentLabelPref(v: unknown): ValidationResult {
|
||||
return lexicons.validate('app.bsky.actor.defs#contentLabelPref', v)
|
||||
}
|
||||
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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 } from '@atproto/xrpc-server'
|
||||
import * as AppBskyActorDefs from './defs'
|
||||
|
||||
export interface QueryParams {}
|
||||
|
||||
export type InputSchema = undefined
|
||||
|
||||
export interface OutputSchema {
|
||||
preferences: AppBskyActorDefs.Preferences
|
||||
[k: string]: unknown
|
||||
}
|
||||
|
||||
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 Handler<HA extends HandlerAuth = never> = (ctx: {
|
||||
auth: HA
|
||||
params: QueryParams
|
||||
input: HandlerInput
|
||||
req: express.Request
|
||||
res: express.Response
|
||||
}) => Promise<HandlerOutput> | HandlerOutput
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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 } from '@atproto/xrpc-server'
|
||||
import * as AppBskyActorDefs from './defs'
|
||||
|
||||
export interface QueryParams {}
|
||||
|
||||
export interface InputSchema {
|
||||
preferences: AppBskyActorDefs.Preferences
|
||||
[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 Handler<HA extends HandlerAuth = never> = (ctx: {
|
||||
auth: HA
|
||||
params: QueryParams
|
||||
input: HandlerInput
|
||||
req: express.Request
|
||||
res: express.Response
|
||||
}) => Promise<HandlerOutput> | HandlerOutput
|
19
packages/pds/src/api/app/bsky/actor/getPreferences.ts
Normal file
19
packages/pds/src/api/app/bsky/actor/getPreferences.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Server } from '../../../../lexicon'
|
||||
import AppContext from '../../../../context'
|
||||
|
||||
export default function (server: Server, ctx: AppContext) {
|
||||
server.app.bsky.actor.getPreferences({
|
||||
auth: ctx.accessVerifier,
|
||||
handler: async ({ auth }) => {
|
||||
const requester = auth.credentials.did
|
||||
const { services, db } = ctx
|
||||
const preferences = await services
|
||||
.account(db)
|
||||
.getPreferences(requester, 'app.bsky')
|
||||
return {
|
||||
encoding: 'application/json',
|
||||
body: { preferences },
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
9
packages/pds/src/api/app/bsky/actor/index.ts
Normal file
9
packages/pds/src/api/app/bsky/actor/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Server } from '../../../../lexicon'
|
||||
import AppContext from '../../../../context'
|
||||
import getPreferences from './getPreferences'
|
||||
import putPreferences from './putPreferences'
|
||||
|
||||
export default function (server: Server, ctx: AppContext) {
|
||||
getPreferences(server, ctx)
|
||||
putPreferences(server, ctx)
|
||||
}
|
28
packages/pds/src/api/app/bsky/actor/putPreferences.ts
Normal file
28
packages/pds/src/api/app/bsky/actor/putPreferences.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { Server } from '../../../../lexicon'
|
||||
import AppContext from '../../../../context'
|
||||
import { UserPreference } from '../../../../services/account'
|
||||
import { InvalidRequestError } from '@atproto/xrpc-server'
|
||||
|
||||
export default function (server: Server, ctx: AppContext) {
|
||||
server.app.bsky.actor.putPreferences({
|
||||
auth: ctx.accessVerifierCheckTakedown,
|
||||
handler: async ({ auth, input }) => {
|
||||
const { preferences } = input.body
|
||||
const requester = auth.credentials.did
|
||||
const { services, db } = ctx
|
||||
const checkedPreferences: UserPreference[] = []
|
||||
for (const pref of preferences) {
|
||||
if (typeof pref.$type === 'string') {
|
||||
checkedPreferences.push(pref as UserPreference)
|
||||
} else {
|
||||
throw new InvalidRequestError('Preference is missing a $type')
|
||||
}
|
||||
}
|
||||
await db.transaction(async (tx) => {
|
||||
await services
|
||||
.account(tx)
|
||||
.putPreferences(requester, checkedPreferences, 'app.bsky')
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
import { Server } from '../../../lexicon'
|
||||
import AppContext from '../../../context'
|
||||
import actor from './actor'
|
||||
import graph from './graph'
|
||||
import notification from './notification'
|
||||
|
||||
export default function (server: Server, ctx: AppContext) {
|
||||
actor(server, ctx)
|
||||
graph(server, ctx)
|
||||
notification(server, ctx)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Kysely } from 'kysely'
|
||||
import * as userAccount from './tables/user-account'
|
||||
import * as userState from './tables/user-state'
|
||||
import * as userPref from './tables/user-pref'
|
||||
import * as didHandle from './tables/did-handle'
|
||||
import * as repoRoot from './tables/repo-root'
|
||||
import * as refreshToken from './tables/refresh-token'
|
||||
@ -27,6 +28,7 @@ export type DatabaseSchemaType = appView.DatabaseSchemaType &
|
||||
appMigration.PartialDB &
|
||||
userAccount.PartialDB &
|
||||
userState.PartialDB &
|
||||
userPref.PartialDB &
|
||||
didHandle.PartialDB &
|
||||
refreshToken.PartialDB &
|
||||
appPassword.PartialDB &
|
||||
|
@ -0,0 +1,26 @@
|
||||
import { Kysely } from 'kysely'
|
||||
import { Dialect } from '..'
|
||||
|
||||
export async function up(db: Kysely<unknown>, dialect: Dialect): Promise<void> {
|
||||
let builder = db.schema.createTable('user_pref')
|
||||
builder =
|
||||
dialect === 'pg'
|
||||
? builder.addColumn('id', 'bigserial', (col) => col.primaryKey())
|
||||
: builder.addColumn('id', 'integer', (col) =>
|
||||
col.autoIncrement().primaryKey(),
|
||||
)
|
||||
await builder
|
||||
.addColumn('did', 'varchar', (col) => col.notNull())
|
||||
.addColumn('name', 'varchar', (col) => col.notNull())
|
||||
.addColumn('valueJson', 'text', (col) => col.notNull())
|
||||
.execute()
|
||||
await db.schema
|
||||
.createIndex('user_pref_did_idx')
|
||||
.on('user_pref')
|
||||
.column('did')
|
||||
.execute()
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<unknown>): Promise<void> {
|
||||
await db.schema.dropTable('user_pref').execute()
|
||||
}
|
@ -47,3 +47,4 @@ export * as _20230508T193807762Z from './20230508T193807762Z-acct-deletion-index
|
||||
export * as _20230508T232711152Z from './20230508T232711152Z-disable-account-invites'
|
||||
export * as _20230509T192324175Z from './20230509T192324175Z-seq-invalidated'
|
||||
export * as _20230511T154721392Z from './20230511T154721392Z-mute-lists'
|
||||
export * as _20230511T171739449Z from './20230511T171739449Z-actor-preferences'
|
||||
|
12
packages/pds/src/db/tables/user-pref.ts
Normal file
12
packages/pds/src/db/tables/user-pref.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { GeneratedAlways } from 'kysely'
|
||||
|
||||
export interface UserPref {
|
||||
id: GeneratedAlways<number>
|
||||
did: string
|
||||
name: string
|
||||
valueJson: string // json
|
||||
}
|
||||
|
||||
export const tableName = 'user_pref'
|
||||
|
||||
export type PartialDB = { [tableName]: UserPref }
|
@ -67,9 +67,11 @@ import * as ComAtprotoSyncListRepos from './types/com/atproto/sync/listRepos'
|
||||
import * as ComAtprotoSyncNotifyOfUpdate from './types/com/atproto/sync/notifyOfUpdate'
|
||||
import * as ComAtprotoSyncRequestCrawl from './types/com/atproto/sync/requestCrawl'
|
||||
import * as ComAtprotoSyncSubscribeRepos from './types/com/atproto/sync/subscribeRepos'
|
||||
import * as AppBskyActorGetPreferences from './types/app/bsky/actor/getPreferences'
|
||||
import * as AppBskyActorGetProfile from './types/app/bsky/actor/getProfile'
|
||||
import * as AppBskyActorGetProfiles from './types/app/bsky/actor/getProfiles'
|
||||
import * as AppBskyActorGetSuggestions from './types/app/bsky/actor/getSuggestions'
|
||||
import * as AppBskyActorPutPreferences from './types/app/bsky/actor/putPreferences'
|
||||
import * as AppBskyActorSearchActors from './types/app/bsky/actor/searchActors'
|
||||
import * as AppBskyActorSearchActorsTypeahead from './types/app/bsky/actor/searchActorsTypeahead'
|
||||
import * as AppBskyFeedGetAuthorFeed from './types/app/bsky/feed/getAuthorFeed'
|
||||
@ -724,6 +726,13 @@ export class ActorNS {
|
||||
this._server = server
|
||||
}
|
||||
|
||||
getPreferences<AV extends AuthVerifier>(
|
||||
cfg: ConfigOf<AV, AppBskyActorGetPreferences.Handler<ExtractAuth<AV>>>,
|
||||
) {
|
||||
const nsid = 'app.bsky.actor.getPreferences' // @ts-ignore
|
||||
return this._server.xrpc.method(nsid, cfg)
|
||||
}
|
||||
|
||||
getProfile<AV extends AuthVerifier>(
|
||||
cfg: ConfigOf<AV, AppBskyActorGetProfile.Handler<ExtractAuth<AV>>>,
|
||||
) {
|
||||
@ -745,6 +754,13 @@ export class ActorNS {
|
||||
return this._server.xrpc.method(nsid, cfg)
|
||||
}
|
||||
|
||||
putPreferences<AV extends AuthVerifier>(
|
||||
cfg: ConfigOf<AV, AppBskyActorPutPreferences.Handler<ExtractAuth<AV>>>,
|
||||
) {
|
||||
const nsid = 'app.bsky.actor.putPreferences' // @ts-ignore
|
||||
return this._server.xrpc.method(nsid, cfg)
|
||||
}
|
||||
|
||||
searchActors<AV extends AuthVerifier>(
|
||||
cfg: ConfigOf<AV, AppBskyActorSearchActors.Handler<ExtractAuth<AV>>>,
|
||||
) {
|
||||
|
@ -3509,6 +3509,66 @@ export const schemaDict = {
|
||||
},
|
||||
},
|
||||
},
|
||||
preferences: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'union',
|
||||
refs: [
|
||||
'lex:app.bsky.actor.defs#adultContentPref',
|
||||
'lex:app.bsky.actor.defs#contentLabelPref',
|
||||
],
|
||||
},
|
||||
},
|
||||
adultContentPref: {
|
||||
type: 'object',
|
||||
required: ['enabled'],
|
||||
properties: {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
contentLabelPref: {
|
||||
type: 'object',
|
||||
required: ['label', 'visibility'],
|
||||
properties: {
|
||||
label: {
|
||||
type: 'string',
|
||||
},
|
||||
visibility: {
|
||||
type: 'string',
|
||||
knownValues: ['show', 'warn', 'hide'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
AppBskyActorGetPreferences: {
|
||||
lexicon: 1,
|
||||
id: 'app.bsky.actor.getPreferences',
|
||||
defs: {
|
||||
main: {
|
||||
type: 'query',
|
||||
description: 'Get private preferences attached to the account.',
|
||||
parameters: {
|
||||
type: 'params',
|
||||
properties: {},
|
||||
},
|
||||
output: {
|
||||
encoding: 'application/json',
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['preferences'],
|
||||
properties: {
|
||||
preferences: {
|
||||
type: 'ref',
|
||||
ref: 'lex:app.bsky.actor.defs#preferences',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
AppBskyActorGetProfile: {
|
||||
@ -3655,6 +3715,29 @@ export const schemaDict = {
|
||||
},
|
||||
},
|
||||
},
|
||||
AppBskyActorPutPreferences: {
|
||||
lexicon: 1,
|
||||
id: 'app.bsky.actor.putPreferences',
|
||||
defs: {
|
||||
main: {
|
||||
type: 'procedure',
|
||||
description: 'Sets the private preferences attached to the account.',
|
||||
input: {
|
||||
encoding: 'application/json',
|
||||
schema: {
|
||||
type: 'object',
|
||||
required: ['preferences'],
|
||||
properties: {
|
||||
preferences: {
|
||||
type: 'ref',
|
||||
ref: 'lex:app.bsky.actor.defs#preferences',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
AppBskyActorSearchActors: {
|
||||
lexicon: 1,
|
||||
id: 'app.bsky.actor.searchActors',
|
||||
@ -5659,10 +5742,12 @@ export const ids = {
|
||||
ComAtprotoSyncRequestCrawl: 'com.atproto.sync.requestCrawl',
|
||||
ComAtprotoSyncSubscribeRepos: 'com.atproto.sync.subscribeRepos',
|
||||
AppBskyActorDefs: 'app.bsky.actor.defs',
|
||||
AppBskyActorGetPreferences: 'app.bsky.actor.getPreferences',
|
||||
AppBskyActorGetProfile: 'app.bsky.actor.getProfile',
|
||||
AppBskyActorGetProfiles: 'app.bsky.actor.getProfiles',
|
||||
AppBskyActorGetSuggestions: 'app.bsky.actor.getSuggestions',
|
||||
AppBskyActorProfile: 'app.bsky.actor.profile',
|
||||
AppBskyActorPutPreferences: 'app.bsky.actor.putPreferences',
|
||||
AppBskyActorSearchActors: 'app.bsky.actor.searchActors',
|
||||
AppBskyActorSearchActorsTypeahead: 'app.bsky.actor.searchActorsTypeahead',
|
||||
AppBskyEmbedExternal: 'app.bsky.embed.external',
|
||||
|
@ -103,3 +103,44 @@ export function isViewerState(v: unknown): v is ViewerState {
|
||||
export function validateViewerState(v: unknown): ValidationResult {
|
||||
return lexicons.validate('app.bsky.actor.defs#viewerState', v)
|
||||
}
|
||||
|
||||
export type Preferences = (
|
||||
| AdultContentPref
|
||||
| ContentLabelPref
|
||||
| { $type: string; [k: string]: unknown }
|
||||
)[]
|
||||
|
||||
export interface AdultContentPref {
|
||||
enabled: boolean
|
||||
[k: string]: unknown
|
||||
}
|
||||
|
||||
export function isAdultContentPref(v: unknown): v is AdultContentPref {
|
||||
return (
|
||||
isObj(v) &&
|
||||
hasProp(v, '$type') &&
|
||||
v.$type === 'app.bsky.actor.defs#adultContentPref'
|
||||
)
|
||||
}
|
||||
|
||||
export function validateAdultContentPref(v: unknown): ValidationResult {
|
||||
return lexicons.validate('app.bsky.actor.defs#adultContentPref', v)
|
||||
}
|
||||
|
||||
export interface ContentLabelPref {
|
||||
label: string
|
||||
visibility: 'show' | 'warn' | 'hide' | (string & {})
|
||||
[k: string]: unknown
|
||||
}
|
||||
|
||||
export function isContentLabelPref(v: unknown): v is ContentLabelPref {
|
||||
return (
|
||||
isObj(v) &&
|
||||
hasProp(v, '$type') &&
|
||||
v.$type === 'app.bsky.actor.defs#contentLabelPref'
|
||||
)
|
||||
}
|
||||
|
||||
export function validateContentLabelPref(v: unknown): ValidationResult {
|
||||
return lexicons.validate('app.bsky.actor.defs#contentLabelPref', v)
|
||||
}
|
||||
|
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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 } from '@atproto/xrpc-server'
|
||||
import * as AppBskyActorDefs from './defs'
|
||||
|
||||
export interface QueryParams {}
|
||||
|
||||
export type InputSchema = undefined
|
||||
|
||||
export interface OutputSchema {
|
||||
preferences: AppBskyActorDefs.Preferences
|
||||
[k: string]: unknown
|
||||
}
|
||||
|
||||
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 Handler<HA extends HandlerAuth = never> = (ctx: {
|
||||
auth: HA
|
||||
params: QueryParams
|
||||
input: HandlerInput
|
||||
req: express.Request
|
||||
res: express.Response
|
||||
}) => Promise<HandlerOutput> | HandlerOutput
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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 } from '@atproto/xrpc-server'
|
||||
import * as AppBskyActorDefs from './defs'
|
||||
|
||||
export interface QueryParams {}
|
||||
|
||||
export interface InputSchema {
|
||||
preferences: AppBskyActorDefs.Preferences
|
||||
[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 Handler<HA extends HandlerAuth = never> = (ctx: {
|
||||
auth: HA
|
||||
params: QueryParams
|
||||
input: HandlerInput
|
||||
req: express.Request
|
||||
res: express.Response
|
||||
}) => Promise<HandlerOutput> | HandlerOutput
|
@ -2,7 +2,7 @@ import { sql } from 'kysely'
|
||||
import { dbLogger as log } from '../../logger'
|
||||
import Database from '../../db'
|
||||
import * as scrypt from '../../db/scrypt'
|
||||
import { UserAccount, UserAccountEntry } from '../../db/tables/user-account'
|
||||
import { UserAccountEntry } from '../../db/tables/user-account'
|
||||
import { DidHandle } from '../../db/tables/did-handle'
|
||||
import { RepoRoot } from '../../db/tables/repo-root'
|
||||
import {
|
||||
@ -505,8 +505,65 @@ export class AccountService {
|
||||
.executeTakeFirst()
|
||||
return res?.lastSeenNotifs
|
||||
}
|
||||
|
||||
async getPreferences(
|
||||
did: string,
|
||||
namespace?: string,
|
||||
): Promise<UserPreference[]> {
|
||||
const prefsRes = await this.db.db
|
||||
.selectFrom('user_pref')
|
||||
.where('did', '=', did)
|
||||
.orderBy('id')
|
||||
.selectAll()
|
||||
.execute()
|
||||
return prefsRes
|
||||
.filter((pref) => !namespace || matchNamespace(namespace, pref.name))
|
||||
.map((pref) => JSON.parse(pref.valueJson))
|
||||
}
|
||||
|
||||
async putPreferences(
|
||||
did: string,
|
||||
values: UserPreference[],
|
||||
namespace: string,
|
||||
): Promise<void> {
|
||||
this.db.assertTransaction()
|
||||
if (!values.every((value) => matchNamespace(namespace, value.$type))) {
|
||||
throw new InvalidRequestError(
|
||||
`Some preferences are not in the ${namespace} namespace`,
|
||||
)
|
||||
}
|
||||
// get all current prefs for user and prep new pref rows
|
||||
const allPrefs = await this.db.db
|
||||
.selectFrom('user_pref')
|
||||
.where('did', '=', did)
|
||||
.select(['id', 'name'])
|
||||
.execute()
|
||||
const putPrefs = values.map((value) => {
|
||||
return {
|
||||
did,
|
||||
name: value.$type,
|
||||
valueJson: JSON.stringify(value),
|
||||
}
|
||||
})
|
||||
const allPrefIdsInNamespace = allPrefs
|
||||
.filter((pref) => matchNamespace(namespace, pref.name))
|
||||
.map((pref) => pref.id)
|
||||
// replace all prefs in given namespace
|
||||
if (allPrefIdsInNamespace.length) {
|
||||
await this.db.db
|
||||
.deleteFrom('user_pref')
|
||||
.where('did', '=', did)
|
||||
.where('id', 'in', allPrefIdsInNamespace)
|
||||
.execute()
|
||||
}
|
||||
if (putPrefs.length) {
|
||||
await this.db.db.insertInto('user_pref').values(putPrefs).execute()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type UserPreference = Record<string, unknown> & { $type: string }
|
||||
|
||||
type CodeDetail = {
|
||||
code: string
|
||||
available: number
|
||||
@ -532,3 +589,7 @@ export class ListKeyset extends TimeCidKeyset<{
|
||||
return { primary: result.indexedAt, secondary: result.handle }
|
||||
}
|
||||
}
|
||||
|
||||
const matchNamespace = (namespace: string, fullname: string) => {
|
||||
return fullname === namespace || fullname.startsWith(`${namespace}.`)
|
||||
}
|
||||
|
188
packages/pds/tests/preferences.test.ts
Normal file
188
packages/pds/tests/preferences.test.ts
Normal file
@ -0,0 +1,188 @@
|
||||
import AtpAgent from '@atproto/api'
|
||||
import { CloseFn, runTestServer, TestServerInfo } from './_util'
|
||||
import { SeedClient } from './seeds/client'
|
||||
import usersSeed from './seeds/users'
|
||||
|
||||
describe('user preferences', () => {
|
||||
let server: TestServerInfo
|
||||
let close: CloseFn
|
||||
let agent: AtpAgent
|
||||
let sc: SeedClient
|
||||
|
||||
beforeAll(async () => {
|
||||
server = await runTestServer({
|
||||
dbPostgresSchema: 'preferences',
|
||||
})
|
||||
close = server.close
|
||||
agent = new AtpAgent({ service: server.url })
|
||||
sc = new SeedClient(agent)
|
||||
await usersSeed(sc)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await close()
|
||||
})
|
||||
|
||||
it('requires auth to set or put preferences.', async () => {
|
||||
const tryPut = agent.api.app.bsky.actor.putPreferences({
|
||||
preferences: [
|
||||
{ $type: 'app.bsky.actor.defs#adultContentPref', enabled: false },
|
||||
],
|
||||
})
|
||||
await expect(tryPut).rejects.toThrow('Authentication Required')
|
||||
const tryGet = agent.api.app.bsky.actor.getPreferences()
|
||||
await expect(tryGet).rejects.toThrow('Authentication Required')
|
||||
})
|
||||
|
||||
it('gets preferences, before any are set.', async () => {
|
||||
const { data } = await agent.api.app.bsky.actor.getPreferences(
|
||||
{},
|
||||
{ headers: sc.getHeaders(sc.dids.alice) },
|
||||
)
|
||||
expect(data).toEqual({
|
||||
preferences: [],
|
||||
})
|
||||
})
|
||||
|
||||
it('only gets preferences in app.bsky namespace.', async () => {
|
||||
const { db, services } = server.ctx
|
||||
await db.transaction(async (tx) => {
|
||||
await services
|
||||
.account(tx)
|
||||
.putPreferences(
|
||||
sc.dids.alice,
|
||||
[{ $type: 'com.atproto.server.defs#unknown' }],
|
||||
'com.atproto',
|
||||
)
|
||||
})
|
||||
const { data } = await agent.api.app.bsky.actor.getPreferences(
|
||||
{},
|
||||
{ headers: sc.getHeaders(sc.dids.alice) },
|
||||
)
|
||||
expect(data).toEqual({ preferences: [] })
|
||||
})
|
||||
|
||||
it('puts preferences, all creates.', async () => {
|
||||
await agent.api.app.bsky.actor.putPreferences(
|
||||
{
|
||||
preferences: [
|
||||
{ $type: 'app.bsky.actor.defs#adultContentPref', enabled: false },
|
||||
{
|
||||
$type: 'app.bsky.actor.defs#contentLabelPref',
|
||||
label: 'dogs',
|
||||
visibility: 'show',
|
||||
},
|
||||
{
|
||||
$type: 'app.bsky.actor.defs#contentLabelPref',
|
||||
label: 'cats',
|
||||
visibility: 'warn',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ headers: sc.getHeaders(sc.dids.alice), encoding: 'application/json' },
|
||||
)
|
||||
const { data } = await agent.api.app.bsky.actor.getPreferences(
|
||||
{},
|
||||
{ headers: sc.getHeaders(sc.dids.alice) },
|
||||
)
|
||||
expect(data).toEqual({
|
||||
preferences: [
|
||||
{ $type: 'app.bsky.actor.defs#adultContentPref', enabled: false },
|
||||
{
|
||||
$type: 'app.bsky.actor.defs#contentLabelPref',
|
||||
label: 'dogs',
|
||||
visibility: 'show',
|
||||
},
|
||||
{
|
||||
$type: 'app.bsky.actor.defs#contentLabelPref',
|
||||
label: 'cats',
|
||||
visibility: 'warn',
|
||||
},
|
||||
],
|
||||
})
|
||||
// Ensure other prefs were not clobbered
|
||||
const { db, services } = server.ctx
|
||||
const otherPrefs = await services
|
||||
.account(db)
|
||||
.getPreferences(sc.dids.alice, 'com.atproto')
|
||||
expect(otherPrefs).toEqual([{ $type: 'com.atproto.server.defs#unknown' }])
|
||||
})
|
||||
|
||||
it('puts preferences, updates and removals.', async () => {
|
||||
await agent.api.app.bsky.actor.putPreferences(
|
||||
{
|
||||
preferences: [
|
||||
{ $type: 'app.bsky.actor.defs#adultContentPref', enabled: true },
|
||||
{
|
||||
$type: 'app.bsky.actor.defs#contentLabelPref',
|
||||
label: 'dogs',
|
||||
visibility: 'warn',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ headers: sc.getHeaders(sc.dids.alice), encoding: 'application/json' },
|
||||
)
|
||||
const { data } = await agent.api.app.bsky.actor.getPreferences(
|
||||
{},
|
||||
{ headers: sc.getHeaders(sc.dids.alice) },
|
||||
)
|
||||
expect(data).toEqual({
|
||||
preferences: [
|
||||
{ $type: 'app.bsky.actor.defs#adultContentPref', enabled: true },
|
||||
{
|
||||
$type: 'app.bsky.actor.defs#contentLabelPref',
|
||||
label: 'dogs',
|
||||
visibility: 'warn',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('puts preferences, clearing them.', async () => {
|
||||
await agent.api.app.bsky.actor.putPreferences(
|
||||
{ preferences: [] },
|
||||
{ headers: sc.getHeaders(sc.dids.alice), encoding: 'application/json' },
|
||||
)
|
||||
const { data } = await agent.api.app.bsky.actor.getPreferences(
|
||||
{},
|
||||
{ headers: sc.getHeaders(sc.dids.alice) },
|
||||
)
|
||||
expect(data).toEqual({ preferences: [] })
|
||||
})
|
||||
|
||||
it('fails putting preferences outside namespace.', async () => {
|
||||
const tryPut = agent.api.app.bsky.actor.putPreferences(
|
||||
{
|
||||
preferences: [
|
||||
{ $type: 'app.bsky.actor.defs#adultContentPref', enabled: false },
|
||||
{
|
||||
$type: 'com.atproto.server.defs#unknown',
|
||||
hello: 'world',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ headers: sc.getHeaders(sc.dids.alice), encoding: 'application/json' },
|
||||
)
|
||||
await expect(tryPut).rejects.toThrow(
|
||||
'Some preferences are not in the app.bsky namespace',
|
||||
)
|
||||
})
|
||||
|
||||
it('fails putting preferences without $type.', async () => {
|
||||
const tryPut = agent.api.app.bsky.actor.putPreferences(
|
||||
{
|
||||
preferences: [
|
||||
{ $type: 'app.bsky.actor.defs#adultContentPref', enabled: false },
|
||||
{
|
||||
label: 'dogs',
|
||||
visibility: 'warn',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ headers: sc.getHeaders(sc.dids.alice), encoding: 'application/json' },
|
||||
)
|
||||
await expect(tryPut).rejects.toThrow(
|
||||
'Input/preferences/1 must be an object which includes the "$type" property',
|
||||
)
|
||||
})
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user