UNPKG

@atproto/ozone

Version:

Backend service for moderating the Bluesky network.

149 lines (129 loc) 3.4 kB
import assert from 'node:assert' import { Selectable } from 'kysely' import { InvalidRequestError } from '@atproto/xrpc-server' import { Database } from '../db' import { Member } from '../db/schema/member' import { Setting, SettingScope } from '../db/schema/setting' import { Option } from '../lexicon/types/tools/ozone/setting/defs' export type SettingServiceCreator = (db: Database) => SettingService export class SettingService { constructor(public db: Database) {} static creator() { return (db: Database) => new SettingService(db) } async query({ limit = 100, scope, did, cursor, prefix, keys, }: { limit: number scope?: 'personal' | 'instance' did?: string cursor?: string prefix?: string keys?: string[] }): Promise<{ options: Selectable<Setting>[] cursor?: string }> { let builder = this.db.db.selectFrom('setting').selectAll() if (prefix) { builder = builder.where('key', 'like', `${prefix}%`) } else if (keys?.length) { builder = builder.where('key', 'in', keys) } if (scope) { builder = builder.where('scope', '=', scope) } if (did) { builder = builder.where('did', '=', did) } if (cursor) { const cursorId = parseInt(cursor, 10) if (isNaN(cursorId)) { throw new InvalidRequestError('invalid cursor') } builder = builder.where('id', '<', cursorId) } const options = await builder.orderBy('id', 'desc').limit(limit).execute() return { options, cursor: options[options.length - 1]?.id.toString(), } } async upsert( option: Omit<Setting, 'id' | 'createdAt' | 'updatedAt'> & { createdAt: Date updatedAt: Date }, ): Promise<void> { await this.db.db .insertInto('setting') .values(option) .onConflict((oc) => { return oc.columns(['key', 'scope', 'did']).doUpdateSet({ value: option.value, updatedAt: option.updatedAt, description: option.description, managerRole: option.managerRole, lastUpdatedBy: option.lastUpdatedBy, }) }) .execute() } async removeOptions( keys: string[], filters: { did?: string scope: SettingScope managerRole: Member['role'][] }, ): Promise<void> { if (!keys.length) return if (filters.scope === 'personal') { assert(filters.did, 'did is required for personal scope') } let qb = this.db.db .deleteFrom('setting') .where('key', 'in', keys) .where('scope', '=', filters.scope) if (filters.managerRole.length) { qb = qb.where('managerRole', 'in', filters.managerRole) } else { qb = qb.where('managerRole', 'is', null) } if (filters.did) { qb = qb.where('did', '=', filters.did) } await qb.execute() } view(setting: Selectable<Setting>): Option { const { key, value, did, description, createdAt, createdBy, updatedAt, lastUpdatedBy, managerRole, scope, } = setting return { key, value, did, scope, createdBy, lastUpdatedBy, managerRole: managerRole || undefined, description: description || undefined, createdAt: createdAt.toISOString(), updatedAt: updatedAt.toISOString(), } } }