diff --git a/.changeset/gold-pets-give.md b/.changeset/gold-pets-give.md new file mode 100644 index 00000000000..88f95f61f1f --- /dev/null +++ b/.changeset/gold-pets-give.md @@ -0,0 +1,5 @@ +--- +"@atproto/api": patch +--- + +Add searchStarterPacks and searchStarterPacksSkeleton diff --git a/lexicons/app/bsky/graph/searchStarterPacks.json b/lexicons/app/bsky/graph/searchStarterPacks.json new file mode 100644 index 00000000000..66a26ec45df --- /dev/null +++ b/lexicons/app/bsky/graph/searchStarterPacks.json @@ -0,0 +1,48 @@ +{ + "lexicon": 1, + "id": "app.bsky.graph.searchStarterPacks", + "defs": { + "main": { + "type": "query", + "description": "Find starter packs matching search criteria. Does not require auth.", + "parameters": { + "type": "params", + "required": ["q"], + "properties": { + "q": { + "type": "string", + "description": "Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + }, + "cursor": { + "type": "string" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["starterPacks"], + "properties": { + "cursor": { + "type": "string" + }, + "starterPacks": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.graph.defs#starterPackViewBasic" + } + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/defs.json b/lexicons/app/bsky/unspecced/defs.json index e9925922a3e..2391181d8c6 100644 --- a/lexicons/app/bsky/unspecced/defs.json +++ b/lexicons/app/bsky/unspecced/defs.json @@ -15,6 +15,13 @@ "properties": { "did": { "type": "string", "format": "did" } } + }, + "skeletonSearchStarterPack": { + "type": "object", + "required": ["uri"], + "properties": { + "uri": { "type": "string", "format": "at-uri" } + } } } } diff --git a/lexicons/app/bsky/unspecced/searchStarterPacksSkeleton.json b/lexicons/app/bsky/unspecced/searchStarterPacksSkeleton.json new file mode 100644 index 00000000000..80c1b881793 --- /dev/null +++ b/lexicons/app/bsky/unspecced/searchStarterPacksSkeleton.json @@ -0,0 +1,63 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.searchStarterPacksSkeleton", + "defs": { + "main": { + "type": "query", + "description": "Backend Starter Pack search, returns only skeleton.", + "parameters": { + "type": "params", + "required": ["q"], + "properties": { + "q": { + "type": "string", + "description": "Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended." + }, + "viewer": { + "type": "string", + "format": "did", + "description": "DID of the account making the request (not included for public/unauthenticated queries)." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + }, + "cursor": { + "type": "string", + "description": "Optional pagination mechanism; may not necessarily allow scrolling through entire result set." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["starterPacks"], + "properties": { + "cursor": { + "type": "string" + }, + "hitsTotal": { + "type": "integer", + "description": "Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits." + }, + "starterPacks": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.unspecced.defs#skeletonSearchStarterPack" + } + } + } + } + }, + "errors": [ + { + "name": "BadQueryString" + } + ] + } + } +} diff --git a/packages/api/src/client/index.ts b/packages/api/src/client/index.ts index 3850e0abef7..e9b1b3e059a 100644 --- a/packages/api/src/client/index.ts +++ b/packages/api/src/client/index.ts @@ -147,6 +147,7 @@ import * as AppBskyGraphListitem from './types/app/bsky/graph/listitem' import * as AppBskyGraphMuteActor from './types/app/bsky/graph/muteActor' import * as AppBskyGraphMuteActorList from './types/app/bsky/graph/muteActorList' import * as AppBskyGraphMuteThread from './types/app/bsky/graph/muteThread' +import * as AppBskyGraphSearchStarterPacks from './types/app/bsky/graph/searchStarterPacks' import * as AppBskyGraphStarterpack from './types/app/bsky/graph/starterpack' import * as AppBskyGraphUnmuteActor from './types/app/bsky/graph/unmuteActor' import * as AppBskyGraphUnmuteActorList from './types/app/bsky/graph/unmuteActorList' @@ -167,6 +168,7 @@ import * as AppBskyUnspeccedGetSuggestionsSkeleton from './types/app/bsky/unspec import * as AppBskyUnspeccedGetTaggedSuggestions from './types/app/bsky/unspecced/getTaggedSuggestions' import * as AppBskyUnspeccedSearchActorsSkeleton from './types/app/bsky/unspecced/searchActorsSkeleton' import * as AppBskyUnspeccedSearchPostsSkeleton from './types/app/bsky/unspecced/searchPostsSkeleton' +import * as AppBskyUnspeccedSearchStarterPacksSkeleton from './types/app/bsky/unspecced/searchStarterPacksSkeleton' import * as AppBskyVideoDefs from './types/app/bsky/video/defs' import * as AppBskyVideoGetJobStatus from './types/app/bsky/video/getJobStatus' import * as AppBskyVideoGetUploadLimits from './types/app/bsky/video/getUploadLimits' @@ -371,6 +373,7 @@ export * as AppBskyGraphListitem from './types/app/bsky/graph/listitem' export * as AppBskyGraphMuteActor from './types/app/bsky/graph/muteActor' export * as AppBskyGraphMuteActorList from './types/app/bsky/graph/muteActorList' export * as AppBskyGraphMuteThread from './types/app/bsky/graph/muteThread' +export * as AppBskyGraphSearchStarterPacks from './types/app/bsky/graph/searchStarterPacks' export * as AppBskyGraphStarterpack from './types/app/bsky/graph/starterpack' export * as AppBskyGraphUnmuteActor from './types/app/bsky/graph/unmuteActor' export * as AppBskyGraphUnmuteActorList from './types/app/bsky/graph/unmuteActorList' @@ -391,6 +394,7 @@ export * as AppBskyUnspeccedGetSuggestionsSkeleton from './types/app/bsky/unspec export * as AppBskyUnspeccedGetTaggedSuggestions from './types/app/bsky/unspecced/getTaggedSuggestions' export * as AppBskyUnspeccedSearchActorsSkeleton from './types/app/bsky/unspecced/searchActorsSkeleton' export * as AppBskyUnspeccedSearchPostsSkeleton from './types/app/bsky/unspecced/searchPostsSkeleton' +export * as AppBskyUnspeccedSearchStarterPacksSkeleton from './types/app/bsky/unspecced/searchStarterPacksSkeleton' export * as AppBskyVideoDefs from './types/app/bsky/video/defs' export * as AppBskyVideoGetJobStatus from './types/app/bsky/video/getJobStatus' export * as AppBskyVideoGetUploadLimits from './types/app/bsky/video/getUploadLimits' @@ -2430,6 +2434,18 @@ export class AppBskyGraphNS { return this._client.call('app.bsky.graph.muteThread', opts?.qp, data, opts) } + searchStarterPacks( + params?: AppBskyGraphSearchStarterPacks.QueryParams, + opts?: AppBskyGraphSearchStarterPacks.CallOptions, + ): Promise { + return this._client.call( + 'app.bsky.graph.searchStarterPacks', + params, + undefined, + opts, + ) + } + unmuteActor( data?: AppBskyGraphUnmuteActor.InputSchema, opts?: AppBskyGraphUnmuteActor.CallOptions, @@ -3080,6 +3096,22 @@ export class AppBskyUnspeccedNS { throw AppBskyUnspeccedSearchPostsSkeleton.toKnownErr(e) }) } + + searchStarterPacksSkeleton( + params?: AppBskyUnspeccedSearchStarterPacksSkeleton.QueryParams, + opts?: AppBskyUnspeccedSearchStarterPacksSkeleton.CallOptions, + ): Promise { + return this._client + .call( + 'app.bsky.unspecced.searchStarterPacksSkeleton', + params, + undefined, + opts, + ) + .catch((e) => { + throw AppBskyUnspeccedSearchStarterPacksSkeleton.toKnownErr(e) + }) + } } export class AppBskyVideoNS { diff --git a/packages/api/src/client/lexicons.ts b/packages/api/src/client/lexicons.ts index 03821c358c0..af6d524641c 100644 --- a/packages/api/src/client/lexicons.ts +++ b/packages/api/src/client/lexicons.ts @@ -8488,6 +8488,56 @@ export const schemaDict = { }, }, }, + AppBskyGraphSearchStarterPacks: { + lexicon: 1, + id: 'app.bsky.graph.searchStarterPacks', + defs: { + main: { + type: 'query', + description: + 'Find starter packs matching search criteria. Does not require auth.', + parameters: { + type: 'params', + required: ['q'], + properties: { + q: { + type: 'string', + description: + 'Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended.', + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 25, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['starterPacks'], + properties: { + cursor: { + type: 'string', + }, + starterPacks: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.graph.defs#starterPackViewBasic', + }, + }, + }, + }, + }, + }, + }, + }, AppBskyGraphStarterpack: { lexicon: 1, id: 'app.bsky.graph.starterpack', @@ -9153,6 +9203,16 @@ export const schemaDict = { }, }, }, + skeletonSearchStarterPack: { + type: 'object', + required: ['uri'], + properties: { + uri: { + type: 'string', + format: 'at-uri', + }, + }, + }, }, }, AppBskyUnspeccedGetConfig: { @@ -9526,6 +9586,73 @@ export const schemaDict = { }, }, }, + AppBskyUnspeccedSearchStarterPacksSkeleton: { + lexicon: 1, + id: 'app.bsky.unspecced.searchStarterPacksSkeleton', + defs: { + main: { + type: 'query', + description: 'Backend Starter Pack search, returns only skeleton.', + parameters: { + type: 'params', + required: ['q'], + properties: { + q: { + type: 'string', + description: + 'Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended.', + }, + viewer: { + type: 'string', + format: 'did', + description: + 'DID of the account making the request (not included for public/unauthenticated queries).', + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 25, + }, + cursor: { + type: 'string', + description: + 'Optional pagination mechanism; may not necessarily allow scrolling through entire result set.', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['starterPacks'], + properties: { + cursor: { + type: 'string', + }, + hitsTotal: { + type: 'integer', + description: + 'Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits.', + }, + starterPacks: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.unspecced.defs#skeletonSearchStarterPack', + }, + }, + }, + }, + }, + errors: [ + { + name: 'BadQueryString', + }, + ], + }, + }, + }, AppBskyVideoDefs: { lexicon: 1, id: 'app.bsky.video.defs', @@ -13486,6 +13613,7 @@ export const ids = { AppBskyGraphMuteActor: 'app.bsky.graph.muteActor', AppBskyGraphMuteActorList: 'app.bsky.graph.muteActorList', AppBskyGraphMuteThread: 'app.bsky.graph.muteThread', + AppBskyGraphSearchStarterPacks: 'app.bsky.graph.searchStarterPacks', AppBskyGraphStarterpack: 'app.bsky.graph.starterpack', AppBskyGraphUnmuteActor: 'app.bsky.graph.unmuteActor', AppBskyGraphUnmuteActorList: 'app.bsky.graph.unmuteActorList', @@ -13511,6 +13639,8 @@ export const ids = { AppBskyUnspeccedSearchActorsSkeleton: 'app.bsky.unspecced.searchActorsSkeleton', AppBskyUnspeccedSearchPostsSkeleton: 'app.bsky.unspecced.searchPostsSkeleton', + AppBskyUnspeccedSearchStarterPacksSkeleton: + 'app.bsky.unspecced.searchStarterPacksSkeleton', AppBskyVideoDefs: 'app.bsky.video.defs', AppBskyVideoGetJobStatus: 'app.bsky.video.getJobStatus', AppBskyVideoGetUploadLimits: 'app.bsky.video.getUploadLimits', diff --git a/packages/api/src/client/types/app/bsky/graph/searchStarterPacks.ts b/packages/api/src/client/types/app/bsky/graph/searchStarterPacks.ts new file mode 100644 index 00000000000..8291b219a62 --- /dev/null +++ b/packages/api/src/client/types/app/bsky/graph/searchStarterPacks.ts @@ -0,0 +1,39 @@ +/** + * 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 AppBskyGraphDefs from './defs' + +export interface QueryParams { + /** Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. */ + q: string + limit?: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + starterPacks: AppBskyGraphDefs.StarterPackViewBasic[] + [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 +} diff --git a/packages/api/src/client/types/app/bsky/unspecced/defs.ts b/packages/api/src/client/types/app/bsky/unspecced/defs.ts index ecee03578af..7b6962f4e52 100644 --- a/packages/api/src/client/types/app/bsky/unspecced/defs.ts +++ b/packages/api/src/client/types/app/bsky/unspecced/defs.ts @@ -39,3 +39,27 @@ export function isSkeletonSearchActor(v: unknown): v is SkeletonSearchActor { export function validateSkeletonSearchActor(v: unknown): ValidationResult { return lexicons.validate('app.bsky.unspecced.defs#skeletonSearchActor', v) } + +export interface SkeletonSearchStarterPack { + uri: string + [k: string]: unknown +} + +export function isSkeletonSearchStarterPack( + v: unknown, +): v is SkeletonSearchStarterPack { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.unspecced.defs#skeletonSearchStarterPack' + ) +} + +export function validateSkeletonSearchStarterPack( + v: unknown, +): ValidationResult { + return lexicons.validate( + 'app.bsky.unspecced.defs#skeletonSearchStarterPack', + v, + ) +} diff --git a/packages/api/src/client/types/app/bsky/unspecced/searchStarterPacksSkeleton.ts b/packages/api/src/client/types/app/bsky/unspecced/searchStarterPacksSkeleton.ts new file mode 100644 index 00000000000..c331b86d3e1 --- /dev/null +++ b/packages/api/src/client/types/app/bsky/unspecced/searchStarterPacksSkeleton.ts @@ -0,0 +1,54 @@ +/** + * 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 AppBskyUnspeccedDefs from './defs' + +export interface QueryParams { + /** Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. */ + q: string + /** DID of the account making the request (not included for public/unauthenticated queries). */ + viewer?: string + limit?: number + /** Optional pagination mechanism; may not necessarily allow scrolling through entire result set. */ + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + /** Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits. */ + hitsTotal?: number + starterPacks: AppBskyUnspeccedDefs.SkeletonSearchStarterPack[] + [k: string]: unknown +} + +export interface CallOptions { + signal?: AbortSignal + headers?: HeadersMap +} + +export interface Response { + success: boolean + headers: HeadersMap + data: OutputSchema +} + +export class BadQueryStringError 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 === 'BadQueryString') return new BadQueryStringError(e) + } + + return e +} diff --git a/packages/bsky/proto/bsky.proto b/packages/bsky/proto/bsky.proto index f1f5aa30afd..699b9fe673f 100644 --- a/packages/bsky/proto/bsky.proto +++ b/packages/bsky/proto/bsky.proto @@ -906,6 +906,19 @@ message SearchPostsResponse { string cursor = 2; } +// - Return uris of starter packs matching term, paginated +// - `searchStarterPacks` skeleton +message SearchStarterPacksRequest { + string term = 1; + int32 limit = 2; + string cursor = 3; +} + +message SearchStarterPacksResponse { + repeated string uris = 1; + string cursor = 2; +} + // // Suggestions // @@ -1191,6 +1204,7 @@ service Service { // Search rpc SearchActors(SearchActorsRequest) returns (SearchActorsResponse); rpc SearchPosts(SearchPostsRequest) returns (SearchPostsResponse); + rpc SearchStarterPacks(SearchStarterPacksRequest) returns (SearchStarterPacksResponse); // Suggestions rpc GetFollowSuggestions(GetFollowSuggestionsRequest) returns (GetFollowSuggestionsResponse); diff --git a/packages/bsky/src/api/app/bsky/graph/searchStarterPacks.ts b/packages/bsky/src/api/app/bsky/graph/searchStarterPacks.ts new file mode 100644 index 00000000000..53ca0dba859 --- /dev/null +++ b/packages/bsky/src/api/app/bsky/graph/searchStarterPacks.ts @@ -0,0 +1,118 @@ +import AppContext from '../../../../context' +import { Server } from '../../../../lexicon' +import { mapDefined } from '@atproto/common' +import { AtpAgent, AtUri } from '@atproto/api' +import { QueryParams } from '../../../../lexicon/types/app/bsky/graph/searchStarterPacks' +import { + HydrationFnInput, + PresentationFnInput, + RulesFnInput, + SkeletonFnInput, + createPipeline, +} from '../../../../pipeline' +import { HydrateCtx, Hydrator } from '../../../../hydration/hydrator' +import { Views } from '../../../../views' +import { DataPlaneClient } from '../../../../data-plane' +import { parseString } from '../../../../hydration/util' +import { resHeaders } from '../../../util' +import { uriToDid as creatorFromUri } from '../../../../util/uris' + +export default function (server: Server, ctx: AppContext) { + const searchStarterPacks = createPipeline( + skeleton, + hydration, + noBlocks, + presentation, + ) + server.app.bsky.graph.searchStarterPacks({ + auth: ctx.authVerifier.standardOptional, + handler: async ({ auth, params, req }) => { + const { viewer, includeTakedowns } = ctx.authVerifier.parseCreds(auth) + const labelers = ctx.reqLabelers(req) + const hydrateCtx = await ctx.hydrator.createContext({ + viewer, + labelers, + includeTakedowns, + }) + const results = await searchStarterPacks({ ...params, hydrateCtx }, ctx) + return { + encoding: 'application/json', + body: results, + headers: resHeaders({ labelers: hydrateCtx.labelers }), + } + }, + }) +} + +const skeleton = async (inputs: SkeletonFnInput) => { + const { ctx, params } = inputs + const { q } = params + + if (ctx.searchAgent) { + // @NOTE cursors won't change on appview swap + const { data: res } = + await ctx.searchAgent.app.bsky.unspecced.searchStarterPacksSkeleton({ + q, + cursor: params.cursor, + limit: params.limit, + viewer: params.hydrateCtx.viewer ?? undefined, + }) + return { + uris: res.starterPacks.map(({ uri }) => uri), + cursor: parseString(res.cursor), + } + } + + const res = await ctx.dataplane.searchStarterPacks({ + term: q, + limit: params.limit, + cursor: params.cursor, + }) + return { + uris: res.uris, + cursor: parseString(res.cursor), + } +} + +const hydration = async ( + inputs: HydrationFnInput, +) => { + const { ctx, params, skeleton } = inputs + return ctx.hydrator.hydrateStarterPacksBasic(skeleton.uris, params.hydrateCtx) +} + +const noBlocks = (inputs: RulesFnInput) => { + const { ctx, skeleton, hydration } = inputs + skeleton.uris = skeleton.uris.filter((uri) => { + const creator = creatorFromUri(uri) + return !ctx.views.viewerBlockExists(creator, hydration) + }) + return skeleton +} + +const presentation = ( + inputs: PresentationFnInput, +) => { + const { ctx, skeleton, hydration } = inputs + const starterPacks = mapDefined(skeleton.uris, (uri) => + ctx.views.starterPackBasic(uri, hydration), + ) + return { + starterPacks: starterPacks, + cursor: skeleton.cursor, + } +} + +type Context = { + dataplane: DataPlaneClient + hydrator: Hydrator + views: Views + searchAgent?: AtpAgent +} + +type Params = QueryParams & { hydrateCtx: HydrateCtx } + +type Skeleton = { + uris: string[] + cursor?: string +} diff --git a/packages/bsky/src/api/index.ts b/packages/bsky/src/api/index.ts index 62916b3bd54..e869c908280 100644 --- a/packages/bsky/src/api/index.ts +++ b/packages/bsky/src/api/index.ts @@ -30,6 +30,7 @@ import getMutes from './app/bsky/graph/getMutes' import getRelationships from './app/bsky/graph/getRelationships' import getStarterPack from './app/bsky/graph/getStarterPack' import getStarterPacks from './app/bsky/graph/getStarterPacks' +import searchStarterPacks from './app/bsky/graph/searchStarterPacks' import muteActor from './app/bsky/graph/muteActor' import unmuteActor from './app/bsky/graph/unmuteActor' import muteActorList from './app/bsky/graph/muteActorList' @@ -95,6 +96,7 @@ export default function (server: Server, ctx: AppContext) { getRelationships(server, ctx) getStarterPack(server, ctx) getStarterPacks(server, ctx) + searchStarterPacks(server, ctx) muteActor(server, ctx) unmuteActor(server, ctx) muteActorList(server, ctx) diff --git a/packages/bsky/src/data-plane/server/db/migrations/20241114T153108102Z-add-starter-packs-name.ts b/packages/bsky/src/data-plane/server/db/migrations/20241114T153108102Z-add-starter-packs-name.ts new file mode 100644 index 00000000000..2ae6f0055c0 --- /dev/null +++ b/packages/bsky/src/data-plane/server/db/migrations/20241114T153108102Z-add-starter-packs-name.ts @@ -0,0 +1,19 @@ +import { Kysely, sql } from 'kysely' + +export async function up(db: Kysely): Promise { + await db.schema + .alterTable('starter_pack') + .addColumn('name', 'varchar') + .execute() + + await db.schema // Supports starter pack search + .createIndex(`starter_pack_name_tgrm_idx`) + .on('starter_pack') + .using('gist') + .expression(sql`"name" gist_trgm_ops`) + .execute() +} + +export async function down(db: Kysely): Promise { + await db.schema.alterTable('starter_pack').dropColumn('name').execute() +} diff --git a/packages/bsky/src/data-plane/server/db/migrations/index.ts b/packages/bsky/src/data-plane/server/db/migrations/index.ts index 956f44491b6..5b8684a2a78 100644 --- a/packages/bsky/src/data-plane/server/db/migrations/index.ts +++ b/packages/bsky/src/data-plane/server/db/migrations/index.ts @@ -44,3 +44,4 @@ export * as _20240801T193939827Z from './20240801T193939827Z-post-gate' export * as _20240808T224251220Z from './20240808T224251220Z-post-gate-flags' export * as _20240829T211238293Z from './20240829T211238293Z-simplify-actor-sync' export * as _20240831T134810923Z from './20240831T134810923Z-pinned-posts' +export * as _20241114T153108102Z from './20241114T153108102Z-add-starter-packs-name' diff --git a/packages/bsky/src/data-plane/server/db/tables/starter-pack.ts b/packages/bsky/src/data-plane/server/db/tables/starter-pack.ts index 425cdfbb9aa..b87ddf2ba68 100644 --- a/packages/bsky/src/data-plane/server/db/tables/starter-pack.ts +++ b/packages/bsky/src/data-plane/server/db/tables/starter-pack.ts @@ -6,6 +6,7 @@ export interface StarterPack { uri: string cid: string creator: string + name: string createdAt: string indexedAt: string sortAt: GeneratedAlways diff --git a/packages/bsky/src/data-plane/server/indexing/plugins/starter-pack.ts b/packages/bsky/src/data-plane/server/indexing/plugins/starter-pack.ts index f3d94ff81e9..844c84544a9 100644 --- a/packages/bsky/src/data-plane/server/indexing/plugins/starter-pack.ts +++ b/packages/bsky/src/data-plane/server/indexing/plugins/starter-pack.ts @@ -24,6 +24,7 @@ const insertFn = async ( uri: uri.toString(), cid: cid.toString(), creator: uri.host, + name: obj.name, createdAt: normalizeDatetimeAlways(obj.createdAt), indexedAt: timestamp, }) diff --git a/packages/bsky/src/data-plane/server/routes/search.ts b/packages/bsky/src/data-plane/server/routes/search.ts index 7b3b12d8c29..d12f26fefb1 100644 --- a/packages/bsky/src/data-plane/server/routes/search.ts +++ b/packages/bsky/src/data-plane/server/routes/search.ts @@ -55,6 +55,36 @@ export default (db: Database): Partial> => ({ cursor: keyset.packFromResult(res), } }, + + async searchStarterPacks(req) { + const { term, limit, cursor } = req + const { ref } = db.db.dynamic + let builder = db.db + .selectFrom('starter_pack') + .where('starter_pack.name', 'ilike', `%${term}%`) + .selectAll() + + const keyset = new TimeCidKeyset( + ref('starter_pack.sortAt'), + ref('starter_pack.cid'), + ) + + builder = paginate(builder, { + limit, + cursor, + keyset, + tryIndex: true, + }) + + const res = await builder.execute() + + const cur = keyset.packFromResult(res) + + return { + uris: res.map((row) => row.uri), + cursor: cur, + } + }, }) // Remove leading @ in case a handle is input that way diff --git a/packages/bsky/src/lexicon/index.ts b/packages/bsky/src/lexicon/index.ts index 8002f05911a..0eb29893730 100644 --- a/packages/bsky/src/lexicon/index.ts +++ b/packages/bsky/src/lexicon/index.ts @@ -125,6 +125,7 @@ import * as AppBskyGraphGetSuggestedFollowsByActor from './types/app/bsky/graph/ import * as AppBskyGraphMuteActor from './types/app/bsky/graph/muteActor' import * as AppBskyGraphMuteActorList from './types/app/bsky/graph/muteActorList' import * as AppBskyGraphMuteThread from './types/app/bsky/graph/muteThread' +import * as AppBskyGraphSearchStarterPacks from './types/app/bsky/graph/searchStarterPacks' import * as AppBskyGraphUnmuteActor from './types/app/bsky/graph/unmuteActor' import * as AppBskyGraphUnmuteActorList from './types/app/bsky/graph/unmuteActorList' import * as AppBskyGraphUnmuteThread from './types/app/bsky/graph/unmuteThread' @@ -140,6 +141,7 @@ import * as AppBskyUnspeccedGetSuggestionsSkeleton from './types/app/bsky/unspec import * as AppBskyUnspeccedGetTaggedSuggestions from './types/app/bsky/unspecced/getTaggedSuggestions' import * as AppBskyUnspeccedSearchActorsSkeleton from './types/app/bsky/unspecced/searchActorsSkeleton' import * as AppBskyUnspeccedSearchPostsSkeleton from './types/app/bsky/unspecced/searchPostsSkeleton' +import * as AppBskyUnspeccedSearchStarterPacksSkeleton from './types/app/bsky/unspecced/searchStarterPacksSkeleton' import * as AppBskyVideoGetJobStatus from './types/app/bsky/video/getJobStatus' import * as AppBskyVideoGetUploadLimits from './types/app/bsky/video/getUploadLimits' import * as AppBskyVideoUploadVideo from './types/app/bsky/video/uploadVideo' @@ -1653,6 +1655,17 @@ export class AppBskyGraphNS { return this._server.xrpc.method(nsid, cfg) } + searchStarterPacks( + cfg: ConfigOf< + AV, + AppBskyGraphSearchStarterPacks.Handler>, + AppBskyGraphSearchStarterPacks.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.graph.searchStarterPacks' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + unmuteActor( cfg: ConfigOf< AV, @@ -1849,6 +1862,17 @@ export class AppBskyUnspeccedNS { const nsid = 'app.bsky.unspecced.searchPostsSkeleton' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + + searchStarterPacksSkeleton( + cfg: ConfigOf< + AV, + AppBskyUnspeccedSearchStarterPacksSkeleton.Handler>, + AppBskyUnspeccedSearchStarterPacksSkeleton.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.unspecced.searchStarterPacksSkeleton' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } } export class AppBskyVideoNS { diff --git a/packages/bsky/src/lexicon/lexicons.ts b/packages/bsky/src/lexicon/lexicons.ts index 79ce64e49e7..d564ae8824a 100644 --- a/packages/bsky/src/lexicon/lexicons.ts +++ b/packages/bsky/src/lexicon/lexicons.ts @@ -8488,6 +8488,56 @@ export const schemaDict = { }, }, }, + AppBskyGraphSearchStarterPacks: { + lexicon: 1, + id: 'app.bsky.graph.searchStarterPacks', + defs: { + main: { + type: 'query', + description: + 'Find starter packs matching search criteria. Does not require auth.', + parameters: { + type: 'params', + required: ['q'], + properties: { + q: { + type: 'string', + description: + 'Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended.', + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 25, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['starterPacks'], + properties: { + cursor: { + type: 'string', + }, + starterPacks: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.graph.defs#starterPackViewBasic', + }, + }, + }, + }, + }, + }, + }, + }, AppBskyGraphStarterpack: { lexicon: 1, id: 'app.bsky.graph.starterpack', @@ -9153,6 +9203,16 @@ export const schemaDict = { }, }, }, + skeletonSearchStarterPack: { + type: 'object', + required: ['uri'], + properties: { + uri: { + type: 'string', + format: 'at-uri', + }, + }, + }, }, }, AppBskyUnspeccedGetConfig: { @@ -9526,6 +9586,73 @@ export const schemaDict = { }, }, }, + AppBskyUnspeccedSearchStarterPacksSkeleton: { + lexicon: 1, + id: 'app.bsky.unspecced.searchStarterPacksSkeleton', + defs: { + main: { + type: 'query', + description: 'Backend Starter Pack search, returns only skeleton.', + parameters: { + type: 'params', + required: ['q'], + properties: { + q: { + type: 'string', + description: + 'Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended.', + }, + viewer: { + type: 'string', + format: 'did', + description: + 'DID of the account making the request (not included for public/unauthenticated queries).', + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 25, + }, + cursor: { + type: 'string', + description: + 'Optional pagination mechanism; may not necessarily allow scrolling through entire result set.', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['starterPacks'], + properties: { + cursor: { + type: 'string', + }, + hitsTotal: { + type: 'integer', + description: + 'Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits.', + }, + starterPacks: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.unspecced.defs#skeletonSearchStarterPack', + }, + }, + }, + }, + }, + errors: [ + { + name: 'BadQueryString', + }, + ], + }, + }, + }, AppBskyVideoDefs: { lexicon: 1, id: 'app.bsky.video.defs', @@ -10725,6 +10852,7 @@ export const ids = { AppBskyGraphMuteActor: 'app.bsky.graph.muteActor', AppBskyGraphMuteActorList: 'app.bsky.graph.muteActorList', AppBskyGraphMuteThread: 'app.bsky.graph.muteThread', + AppBskyGraphSearchStarterPacks: 'app.bsky.graph.searchStarterPacks', AppBskyGraphStarterpack: 'app.bsky.graph.starterpack', AppBskyGraphUnmuteActor: 'app.bsky.graph.unmuteActor', AppBskyGraphUnmuteActorList: 'app.bsky.graph.unmuteActorList', @@ -10750,6 +10878,8 @@ export const ids = { AppBskyUnspeccedSearchActorsSkeleton: 'app.bsky.unspecced.searchActorsSkeleton', AppBskyUnspeccedSearchPostsSkeleton: 'app.bsky.unspecced.searchPostsSkeleton', + AppBskyUnspeccedSearchStarterPacksSkeleton: + 'app.bsky.unspecced.searchStarterPacksSkeleton', AppBskyVideoDefs: 'app.bsky.video.defs', AppBskyVideoGetJobStatus: 'app.bsky.video.getJobStatus', AppBskyVideoGetUploadLimits: 'app.bsky.video.getUploadLimits', diff --git a/packages/bsky/src/lexicon/types/app/bsky/graph/searchStarterPacks.ts b/packages/bsky/src/lexicon/types/app/bsky/graph/searchStarterPacks.ts new file mode 100644 index 00000000000..dea496aa50f --- /dev/null +++ b/packages/bsky/src/lexicon/types/app/bsky/graph/searchStarterPacks.ts @@ -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' +import * as AppBskyGraphDefs from './defs' + +export interface QueryParams { + /** Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. */ + q: string + limit: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + starterPacks: AppBskyGraphDefs.StarterPackViewBasic[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/lexicon/types/app/bsky/unspecced/defs.ts b/packages/bsky/src/lexicon/types/app/bsky/unspecced/defs.ts index 59a6b38064c..3411f01599f 100644 --- a/packages/bsky/src/lexicon/types/app/bsky/unspecced/defs.ts +++ b/packages/bsky/src/lexicon/types/app/bsky/unspecced/defs.ts @@ -39,3 +39,27 @@ export function isSkeletonSearchActor(v: unknown): v is SkeletonSearchActor { export function validateSkeletonSearchActor(v: unknown): ValidationResult { return lexicons.validate('app.bsky.unspecced.defs#skeletonSearchActor', v) } + +export interface SkeletonSearchStarterPack { + uri: string + [k: string]: unknown +} + +export function isSkeletonSearchStarterPack( + v: unknown, +): v is SkeletonSearchStarterPack { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.unspecced.defs#skeletonSearchStarterPack' + ) +} + +export function validateSkeletonSearchStarterPack( + v: unknown, +): ValidationResult { + return lexicons.validate( + 'app.bsky.unspecced.defs#skeletonSearchStarterPack', + v, + ) +} diff --git a/packages/bsky/src/lexicon/types/app/bsky/unspecced/searchStarterPacksSkeleton.ts b/packages/bsky/src/lexicon/types/app/bsky/unspecced/searchStarterPacksSkeleton.ts new file mode 100644 index 00000000000..e980d884fec --- /dev/null +++ b/packages/bsky/src/lexicon/types/app/bsky/unspecced/searchStarterPacksSkeleton.ts @@ -0,0 +1,56 @@ +/** + * 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 AppBskyUnspeccedDefs from './defs' + +export interface QueryParams { + /** Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. */ + q: string + /** DID of the account making the request (not included for public/unauthenticated queries). */ + viewer?: string + limit: number + /** Optional pagination mechanism; may not necessarily allow scrolling through entire result set. */ + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + /** Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits. */ + hitsTotal?: number + starterPacks: AppBskyUnspeccedDefs.SkeletonSearchStarterPack[] + [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?: 'BadQueryString' +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/bsky/src/proto/bsky_connect.ts b/packages/bsky/src/proto/bsky_connect.ts index 42303cbe8e8..f26731731f1 100644 --- a/packages/bsky/src/proto/bsky_connect.ts +++ b/packages/bsky/src/proto/bsky_connect.ts @@ -176,6 +176,8 @@ import { SearchFeedGeneratorsResponse, SearchPostsRequest, SearchPostsResponse, + SearchStarterPacksRequest, + SearchStarterPacksResponse, TakedownActorRequest, TakedownActorResponse, TakedownBlobRequest, @@ -819,6 +821,15 @@ export const Service = { O: SearchPostsResponse, kind: MethodKind.Unary, }, + /** + * @generated from rpc bsky.Service.SearchStarterPacks + */ + searchStarterPacks: { + name: 'SearchStarterPacks', + I: SearchStarterPacksRequest, + O: SearchStarterPacksResponse, + kind: MethodKind.Unary, + }, /** * Suggestions * diff --git a/packages/bsky/src/proto/bsky_pb.ts b/packages/bsky/src/proto/bsky_pb.ts index 594d4eb03b4..74f0f04a604 100644 --- a/packages/bsky/src/proto/bsky_pb.ts +++ b/packages/bsky/src/proto/bsky_pb.ts @@ -9445,6 +9445,143 @@ export class SearchPostsResponse extends Message { } } +/** + * - Return uris of starter packs matching term, paginated + * - `searchStarterPacks` skeleton + * + * @generated from message bsky.SearchStarterPacksRequest + */ +export class SearchStarterPacksRequest extends Message { + /** + * @generated from field: string term = 1; + */ + term = '' + + /** + * @generated from field: int32 limit = 2; + */ + limit = 0 + + /** + * @generated from field: string cursor = 3; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.SearchStarterPacksRequest' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: 'term', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + { no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ }, + { no: 3, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): SearchStarterPacksRequest { + return new SearchStarterPacksRequest().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): SearchStarterPacksRequest { + return new SearchStarterPacksRequest().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): SearchStarterPacksRequest { + return new SearchStarterPacksRequest().fromJsonString(jsonString, options) + } + + static equals( + a: + | SearchStarterPacksRequest + | PlainMessage + | undefined, + b: + | SearchStarterPacksRequest + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(SearchStarterPacksRequest, a, b) + } +} + +/** + * @generated from message bsky.SearchStarterPacksResponse + */ +export class SearchStarterPacksResponse extends Message { + /** + * @generated from field: repeated string uris = 1; + */ + uris: string[] = [] + + /** + * @generated from field: string cursor = 2; + */ + cursor = '' + + constructor(data?: PartialMessage) { + super() + proto3.util.initPartial(data, this) + } + + static readonly runtime: typeof proto3 = proto3 + static readonly typeName = 'bsky.SearchStarterPacksResponse' + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { + no: 1, + name: 'uris', + kind: 'scalar', + T: 9 /* ScalarType.STRING */, + repeated: true, + }, + { no: 2, name: 'cursor', kind: 'scalar', T: 9 /* ScalarType.STRING */ }, + ]) + + static fromBinary( + bytes: Uint8Array, + options?: Partial, + ): SearchStarterPacksResponse { + return new SearchStarterPacksResponse().fromBinary(bytes, options) + } + + static fromJson( + jsonValue: JsonValue, + options?: Partial, + ): SearchStarterPacksResponse { + return new SearchStarterPacksResponse().fromJson(jsonValue, options) + } + + static fromJsonString( + jsonString: string, + options?: Partial, + ): SearchStarterPacksResponse { + return new SearchStarterPacksResponse().fromJsonString(jsonString, options) + } + + static equals( + a: + | SearchStarterPacksResponse + | PlainMessage + | undefined, + b: + | SearchStarterPacksResponse + | PlainMessage + | undefined, + ): boolean { + return proto3.util.equals(SearchStarterPacksResponse, a, b) + } +} + /** * - Return DIDs of suggested follows for a user, excluding anyone they already follow * - `getSuggestions`, `getSuggestedFollowsByActor` diff --git a/packages/bsky/tests/views/starter-packs.test.ts b/packages/bsky/tests/views/starter-packs.test.ts index 9e844b7d155..d78e93d0d9f 100644 --- a/packages/bsky/tests/views/starter-packs.test.ts +++ b/packages/bsky/tests/views/starter-packs.test.ts @@ -12,6 +12,7 @@ describe('starter packs', () => { let sp1: RecordRef let sp2: RecordRef let sp3: RecordRef + let sp4: RecordRef beforeAll(async () => { network = await TestNetwork.create({ @@ -68,6 +69,13 @@ describe('starter packs', () => { ) await sc.block(sc.dids.frankie, sc.dids.alice) + sp4 = await sc.createStarterPack( + sc.dids.bob, + "bob's starter pack in case you block alice", + [sc.dids.alice, sc.dids.frankie], + [], + ) + await network.processAll() }) @@ -198,4 +206,58 @@ describe('starter packs', () => { expect(view.data.starterPack.listItemsSample?.length).toBe(3) expect(forSnapshot(view.data.starterPack.listItemsSample)).toMatchSnapshot() }) + + describe('searchStarterPacks', () => { + it('searches starter packs and returns paginated', async () => { + const { data: page0 } = await agent.app.bsky.graph.searchStarterPacks({ + q: 'starter', + limit: 3, + }) + + expect(page0.starterPacks).toMatchObject([ + expect.objectContaining({ uri: sp4.uriStr }), + expect.objectContaining({ uri: sp3.uriStr }), + expect.objectContaining({ uri: sp2.uriStr }), + ]) + + const { data: page1 } = await agent.api.app.bsky.graph.searchStarterPacks( + { + q: 'starter', + limit: 3, + cursor: page0.cursor, + }, + ) + + expect(page1.starterPacks).toMatchObject([ + expect.objectContaining({ uri: sp1.uriStr }), + ]) + }) + + it('filters by the search term', async () => { + const { data } = await agent.app.bsky.graph.searchStarterPacks({ + q: 'In CaSe', + limit: 3, + }) + + expect(data.starterPacks).toMatchObject([ + expect.objectContaining({ uri: sp4.uriStr }), + ]) + }) + + it('does not include starter packs with creator block relationship for non-creator viewers', async () => { + const { data } = await agent.app.bsky.graph.searchStarterPacks( + { q: 'starter', limit: 3 }, + { + headers: await network.serviceHeaders( + sc.dids.frankie, + ids.AppBskyGraphSearchStarterPacks, + ), + }, + ) + + expect(data.starterPacks).toMatchObject([ + expect.objectContaining({ uri: sp4.uriStr }), + ]) + }) + }) }) diff --git a/packages/ozone/src/lexicon/index.ts b/packages/ozone/src/lexicon/index.ts index 9a81406bfa7..7bcf0756578 100644 --- a/packages/ozone/src/lexicon/index.ts +++ b/packages/ozone/src/lexicon/index.ts @@ -125,6 +125,7 @@ import * as AppBskyGraphGetSuggestedFollowsByActor from './types/app/bsky/graph/ import * as AppBskyGraphMuteActor from './types/app/bsky/graph/muteActor' import * as AppBskyGraphMuteActorList from './types/app/bsky/graph/muteActorList' import * as AppBskyGraphMuteThread from './types/app/bsky/graph/muteThread' +import * as AppBskyGraphSearchStarterPacks from './types/app/bsky/graph/searchStarterPacks' import * as AppBskyGraphUnmuteActor from './types/app/bsky/graph/unmuteActor' import * as AppBskyGraphUnmuteActorList from './types/app/bsky/graph/unmuteActorList' import * as AppBskyGraphUnmuteThread from './types/app/bsky/graph/unmuteThread' @@ -140,6 +141,7 @@ import * as AppBskyUnspeccedGetSuggestionsSkeleton from './types/app/bsky/unspec import * as AppBskyUnspeccedGetTaggedSuggestions from './types/app/bsky/unspecced/getTaggedSuggestions' import * as AppBskyUnspeccedSearchActorsSkeleton from './types/app/bsky/unspecced/searchActorsSkeleton' import * as AppBskyUnspeccedSearchPostsSkeleton from './types/app/bsky/unspecced/searchPostsSkeleton' +import * as AppBskyUnspeccedSearchStarterPacksSkeleton from './types/app/bsky/unspecced/searchStarterPacksSkeleton' import * as AppBskyVideoGetJobStatus from './types/app/bsky/video/getJobStatus' import * as AppBskyVideoGetUploadLimits from './types/app/bsky/video/getUploadLimits' import * as AppBskyVideoUploadVideo from './types/app/bsky/video/uploadVideo' @@ -1696,6 +1698,17 @@ export class AppBskyGraphNS { return this._server.xrpc.method(nsid, cfg) } + searchStarterPacks( + cfg: ConfigOf< + AV, + AppBskyGraphSearchStarterPacks.Handler>, + AppBskyGraphSearchStarterPacks.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.graph.searchStarterPacks' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + unmuteActor( cfg: ConfigOf< AV, @@ -1892,6 +1905,17 @@ export class AppBskyUnspeccedNS { const nsid = 'app.bsky.unspecced.searchPostsSkeleton' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + + searchStarterPacksSkeleton( + cfg: ConfigOf< + AV, + AppBskyUnspeccedSearchStarterPacksSkeleton.Handler>, + AppBskyUnspeccedSearchStarterPacksSkeleton.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.unspecced.searchStarterPacksSkeleton' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } } export class AppBskyVideoNS { diff --git a/packages/ozone/src/lexicon/lexicons.ts b/packages/ozone/src/lexicon/lexicons.ts index 03821c358c0..af6d524641c 100644 --- a/packages/ozone/src/lexicon/lexicons.ts +++ b/packages/ozone/src/lexicon/lexicons.ts @@ -8488,6 +8488,56 @@ export const schemaDict = { }, }, }, + AppBskyGraphSearchStarterPacks: { + lexicon: 1, + id: 'app.bsky.graph.searchStarterPacks', + defs: { + main: { + type: 'query', + description: + 'Find starter packs matching search criteria. Does not require auth.', + parameters: { + type: 'params', + required: ['q'], + properties: { + q: { + type: 'string', + description: + 'Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended.', + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 25, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['starterPacks'], + properties: { + cursor: { + type: 'string', + }, + starterPacks: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.graph.defs#starterPackViewBasic', + }, + }, + }, + }, + }, + }, + }, + }, AppBskyGraphStarterpack: { lexicon: 1, id: 'app.bsky.graph.starterpack', @@ -9153,6 +9203,16 @@ export const schemaDict = { }, }, }, + skeletonSearchStarterPack: { + type: 'object', + required: ['uri'], + properties: { + uri: { + type: 'string', + format: 'at-uri', + }, + }, + }, }, }, AppBskyUnspeccedGetConfig: { @@ -9526,6 +9586,73 @@ export const schemaDict = { }, }, }, + AppBskyUnspeccedSearchStarterPacksSkeleton: { + lexicon: 1, + id: 'app.bsky.unspecced.searchStarterPacksSkeleton', + defs: { + main: { + type: 'query', + description: 'Backend Starter Pack search, returns only skeleton.', + parameters: { + type: 'params', + required: ['q'], + properties: { + q: { + type: 'string', + description: + 'Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended.', + }, + viewer: { + type: 'string', + format: 'did', + description: + 'DID of the account making the request (not included for public/unauthenticated queries).', + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 25, + }, + cursor: { + type: 'string', + description: + 'Optional pagination mechanism; may not necessarily allow scrolling through entire result set.', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['starterPacks'], + properties: { + cursor: { + type: 'string', + }, + hitsTotal: { + type: 'integer', + description: + 'Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits.', + }, + starterPacks: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.unspecced.defs#skeletonSearchStarterPack', + }, + }, + }, + }, + }, + errors: [ + { + name: 'BadQueryString', + }, + ], + }, + }, + }, AppBskyVideoDefs: { lexicon: 1, id: 'app.bsky.video.defs', @@ -13486,6 +13613,7 @@ export const ids = { AppBskyGraphMuteActor: 'app.bsky.graph.muteActor', AppBskyGraphMuteActorList: 'app.bsky.graph.muteActorList', AppBskyGraphMuteThread: 'app.bsky.graph.muteThread', + AppBskyGraphSearchStarterPacks: 'app.bsky.graph.searchStarterPacks', AppBskyGraphStarterpack: 'app.bsky.graph.starterpack', AppBskyGraphUnmuteActor: 'app.bsky.graph.unmuteActor', AppBskyGraphUnmuteActorList: 'app.bsky.graph.unmuteActorList', @@ -13511,6 +13639,8 @@ export const ids = { AppBskyUnspeccedSearchActorsSkeleton: 'app.bsky.unspecced.searchActorsSkeleton', AppBskyUnspeccedSearchPostsSkeleton: 'app.bsky.unspecced.searchPostsSkeleton', + AppBskyUnspeccedSearchStarterPacksSkeleton: + 'app.bsky.unspecced.searchStarterPacksSkeleton', AppBskyVideoDefs: 'app.bsky.video.defs', AppBskyVideoGetJobStatus: 'app.bsky.video.getJobStatus', AppBskyVideoGetUploadLimits: 'app.bsky.video.getUploadLimits', diff --git a/packages/ozone/src/lexicon/types/app/bsky/graph/searchStarterPacks.ts b/packages/ozone/src/lexicon/types/app/bsky/graph/searchStarterPacks.ts new file mode 100644 index 00000000000..dea496aa50f --- /dev/null +++ b/packages/ozone/src/lexicon/types/app/bsky/graph/searchStarterPacks.ts @@ -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' +import * as AppBskyGraphDefs from './defs' + +export interface QueryParams { + /** Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. */ + q: string + limit: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + starterPacks: AppBskyGraphDefs.StarterPackViewBasic[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/ozone/src/lexicon/types/app/bsky/unspecced/defs.ts b/packages/ozone/src/lexicon/types/app/bsky/unspecced/defs.ts index 59a6b38064c..3411f01599f 100644 --- a/packages/ozone/src/lexicon/types/app/bsky/unspecced/defs.ts +++ b/packages/ozone/src/lexicon/types/app/bsky/unspecced/defs.ts @@ -39,3 +39,27 @@ export function isSkeletonSearchActor(v: unknown): v is SkeletonSearchActor { export function validateSkeletonSearchActor(v: unknown): ValidationResult { return lexicons.validate('app.bsky.unspecced.defs#skeletonSearchActor', v) } + +export interface SkeletonSearchStarterPack { + uri: string + [k: string]: unknown +} + +export function isSkeletonSearchStarterPack( + v: unknown, +): v is SkeletonSearchStarterPack { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.unspecced.defs#skeletonSearchStarterPack' + ) +} + +export function validateSkeletonSearchStarterPack( + v: unknown, +): ValidationResult { + return lexicons.validate( + 'app.bsky.unspecced.defs#skeletonSearchStarterPack', + v, + ) +} diff --git a/packages/ozone/src/lexicon/types/app/bsky/unspecced/searchStarterPacksSkeleton.ts b/packages/ozone/src/lexicon/types/app/bsky/unspecced/searchStarterPacksSkeleton.ts new file mode 100644 index 00000000000..e980d884fec --- /dev/null +++ b/packages/ozone/src/lexicon/types/app/bsky/unspecced/searchStarterPacksSkeleton.ts @@ -0,0 +1,56 @@ +/** + * 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 AppBskyUnspeccedDefs from './defs' + +export interface QueryParams { + /** Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. */ + q: string + /** DID of the account making the request (not included for public/unauthenticated queries). */ + viewer?: string + limit: number + /** Optional pagination mechanism; may not necessarily allow scrolling through entire result set. */ + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + /** Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits. */ + hitsTotal?: number + starterPacks: AppBskyUnspeccedDefs.SkeletonSearchStarterPack[] + [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?: 'BadQueryString' +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/index.ts b/packages/pds/src/lexicon/index.ts index 9a81406bfa7..7bcf0756578 100644 --- a/packages/pds/src/lexicon/index.ts +++ b/packages/pds/src/lexicon/index.ts @@ -125,6 +125,7 @@ import * as AppBskyGraphGetSuggestedFollowsByActor from './types/app/bsky/graph/ import * as AppBskyGraphMuteActor from './types/app/bsky/graph/muteActor' import * as AppBskyGraphMuteActorList from './types/app/bsky/graph/muteActorList' import * as AppBskyGraphMuteThread from './types/app/bsky/graph/muteThread' +import * as AppBskyGraphSearchStarterPacks from './types/app/bsky/graph/searchStarterPacks' import * as AppBskyGraphUnmuteActor from './types/app/bsky/graph/unmuteActor' import * as AppBskyGraphUnmuteActorList from './types/app/bsky/graph/unmuteActorList' import * as AppBskyGraphUnmuteThread from './types/app/bsky/graph/unmuteThread' @@ -140,6 +141,7 @@ import * as AppBskyUnspeccedGetSuggestionsSkeleton from './types/app/bsky/unspec import * as AppBskyUnspeccedGetTaggedSuggestions from './types/app/bsky/unspecced/getTaggedSuggestions' import * as AppBskyUnspeccedSearchActorsSkeleton from './types/app/bsky/unspecced/searchActorsSkeleton' import * as AppBskyUnspeccedSearchPostsSkeleton from './types/app/bsky/unspecced/searchPostsSkeleton' +import * as AppBskyUnspeccedSearchStarterPacksSkeleton from './types/app/bsky/unspecced/searchStarterPacksSkeleton' import * as AppBskyVideoGetJobStatus from './types/app/bsky/video/getJobStatus' import * as AppBskyVideoGetUploadLimits from './types/app/bsky/video/getUploadLimits' import * as AppBskyVideoUploadVideo from './types/app/bsky/video/uploadVideo' @@ -1696,6 +1698,17 @@ export class AppBskyGraphNS { return this._server.xrpc.method(nsid, cfg) } + searchStarterPacks( + cfg: ConfigOf< + AV, + AppBskyGraphSearchStarterPacks.Handler>, + AppBskyGraphSearchStarterPacks.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.graph.searchStarterPacks' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } + unmuteActor( cfg: ConfigOf< AV, @@ -1892,6 +1905,17 @@ export class AppBskyUnspeccedNS { const nsid = 'app.bsky.unspecced.searchPostsSkeleton' // @ts-ignore return this._server.xrpc.method(nsid, cfg) } + + searchStarterPacksSkeleton( + cfg: ConfigOf< + AV, + AppBskyUnspeccedSearchStarterPacksSkeleton.Handler>, + AppBskyUnspeccedSearchStarterPacksSkeleton.HandlerReqCtx> + >, + ) { + const nsid = 'app.bsky.unspecced.searchStarterPacksSkeleton' // @ts-ignore + return this._server.xrpc.method(nsid, cfg) + } } export class AppBskyVideoNS { diff --git a/packages/pds/src/lexicon/lexicons.ts b/packages/pds/src/lexicon/lexicons.ts index 03821c358c0..af6d524641c 100644 --- a/packages/pds/src/lexicon/lexicons.ts +++ b/packages/pds/src/lexicon/lexicons.ts @@ -8488,6 +8488,56 @@ export const schemaDict = { }, }, }, + AppBskyGraphSearchStarterPacks: { + lexicon: 1, + id: 'app.bsky.graph.searchStarterPacks', + defs: { + main: { + type: 'query', + description: + 'Find starter packs matching search criteria. Does not require auth.', + parameters: { + type: 'params', + required: ['q'], + properties: { + q: { + type: 'string', + description: + 'Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended.', + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 25, + }, + cursor: { + type: 'string', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['starterPacks'], + properties: { + cursor: { + type: 'string', + }, + starterPacks: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.graph.defs#starterPackViewBasic', + }, + }, + }, + }, + }, + }, + }, + }, AppBskyGraphStarterpack: { lexicon: 1, id: 'app.bsky.graph.starterpack', @@ -9153,6 +9203,16 @@ export const schemaDict = { }, }, }, + skeletonSearchStarterPack: { + type: 'object', + required: ['uri'], + properties: { + uri: { + type: 'string', + format: 'at-uri', + }, + }, + }, }, }, AppBskyUnspeccedGetConfig: { @@ -9526,6 +9586,73 @@ export const schemaDict = { }, }, }, + AppBskyUnspeccedSearchStarterPacksSkeleton: { + lexicon: 1, + id: 'app.bsky.unspecced.searchStarterPacksSkeleton', + defs: { + main: { + type: 'query', + description: 'Backend Starter Pack search, returns only skeleton.', + parameters: { + type: 'params', + required: ['q'], + properties: { + q: { + type: 'string', + description: + 'Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended.', + }, + viewer: { + type: 'string', + format: 'did', + description: + 'DID of the account making the request (not included for public/unauthenticated queries).', + }, + limit: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 25, + }, + cursor: { + type: 'string', + description: + 'Optional pagination mechanism; may not necessarily allow scrolling through entire result set.', + }, + }, + }, + output: { + encoding: 'application/json', + schema: { + type: 'object', + required: ['starterPacks'], + properties: { + cursor: { + type: 'string', + }, + hitsTotal: { + type: 'integer', + description: + 'Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits.', + }, + starterPacks: { + type: 'array', + items: { + type: 'ref', + ref: 'lex:app.bsky.unspecced.defs#skeletonSearchStarterPack', + }, + }, + }, + }, + }, + errors: [ + { + name: 'BadQueryString', + }, + ], + }, + }, + }, AppBskyVideoDefs: { lexicon: 1, id: 'app.bsky.video.defs', @@ -13486,6 +13613,7 @@ export const ids = { AppBskyGraphMuteActor: 'app.bsky.graph.muteActor', AppBskyGraphMuteActorList: 'app.bsky.graph.muteActorList', AppBskyGraphMuteThread: 'app.bsky.graph.muteThread', + AppBskyGraphSearchStarterPacks: 'app.bsky.graph.searchStarterPacks', AppBskyGraphStarterpack: 'app.bsky.graph.starterpack', AppBskyGraphUnmuteActor: 'app.bsky.graph.unmuteActor', AppBskyGraphUnmuteActorList: 'app.bsky.graph.unmuteActorList', @@ -13511,6 +13639,8 @@ export const ids = { AppBskyUnspeccedSearchActorsSkeleton: 'app.bsky.unspecced.searchActorsSkeleton', AppBskyUnspeccedSearchPostsSkeleton: 'app.bsky.unspecced.searchPostsSkeleton', + AppBskyUnspeccedSearchStarterPacksSkeleton: + 'app.bsky.unspecced.searchStarterPacksSkeleton', AppBskyVideoDefs: 'app.bsky.video.defs', AppBskyVideoGetJobStatus: 'app.bsky.video.getJobStatus', AppBskyVideoGetUploadLimits: 'app.bsky.video.getUploadLimits', diff --git a/packages/pds/src/lexicon/types/app/bsky/graph/searchStarterPacks.ts b/packages/pds/src/lexicon/types/app/bsky/graph/searchStarterPacks.ts new file mode 100644 index 00000000000..dea496aa50f --- /dev/null +++ b/packages/pds/src/lexicon/types/app/bsky/graph/searchStarterPacks.ts @@ -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' +import * as AppBskyGraphDefs from './defs' + +export interface QueryParams { + /** Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. */ + q: string + limit: number + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + starterPacks: AppBskyGraphDefs.StarterPackViewBasic[] + [k: string]: unknown +} + +export type HandlerInput = undefined + +export interface HandlerSuccess { + encoding: 'application/json' + body: OutputSchema + headers?: { [key: string]: string } +} + +export interface HandlerError { + status: number + message?: string +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput diff --git a/packages/pds/src/lexicon/types/app/bsky/unspecced/defs.ts b/packages/pds/src/lexicon/types/app/bsky/unspecced/defs.ts index 59a6b38064c..3411f01599f 100644 --- a/packages/pds/src/lexicon/types/app/bsky/unspecced/defs.ts +++ b/packages/pds/src/lexicon/types/app/bsky/unspecced/defs.ts @@ -39,3 +39,27 @@ export function isSkeletonSearchActor(v: unknown): v is SkeletonSearchActor { export function validateSkeletonSearchActor(v: unknown): ValidationResult { return lexicons.validate('app.bsky.unspecced.defs#skeletonSearchActor', v) } + +export interface SkeletonSearchStarterPack { + uri: string + [k: string]: unknown +} + +export function isSkeletonSearchStarterPack( + v: unknown, +): v is SkeletonSearchStarterPack { + return ( + isObj(v) && + hasProp(v, '$type') && + v.$type === 'app.bsky.unspecced.defs#skeletonSearchStarterPack' + ) +} + +export function validateSkeletonSearchStarterPack( + v: unknown, +): ValidationResult { + return lexicons.validate( + 'app.bsky.unspecced.defs#skeletonSearchStarterPack', + v, + ) +} diff --git a/packages/pds/src/lexicon/types/app/bsky/unspecced/searchStarterPacksSkeleton.ts b/packages/pds/src/lexicon/types/app/bsky/unspecced/searchStarterPacksSkeleton.ts new file mode 100644 index 00000000000..e980d884fec --- /dev/null +++ b/packages/pds/src/lexicon/types/app/bsky/unspecced/searchStarterPacksSkeleton.ts @@ -0,0 +1,56 @@ +/** + * 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 AppBskyUnspeccedDefs from './defs' + +export interface QueryParams { + /** Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. */ + q: string + /** DID of the account making the request (not included for public/unauthenticated queries). */ + viewer?: string + limit: number + /** Optional pagination mechanism; may not necessarily allow scrolling through entire result set. */ + cursor?: string +} + +export type InputSchema = undefined + +export interface OutputSchema { + cursor?: string + /** Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits. */ + hitsTotal?: number + starterPacks: AppBskyUnspeccedDefs.SkeletonSearchStarterPack[] + [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?: 'BadQueryString' +} + +export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough +export type HandlerReqCtx = { + auth: HA + params: QueryParams + input: HandlerInput + req: express.Request + res: express.Response +} +export type Handler = ( + ctx: HandlerReqCtx, +) => Promise | HandlerOutput