Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Appview v2 search for feed generators #2118

Merged
merged 2 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/bsky/proto/bsky.proto
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,15 @@ message GetSuggestedFeedsResponse {
string cursor = 2;
}

message SearchFeedGeneratorsRequest {
string query = 1;
int32 limit = 2;
}

message SearchFeedGeneratorsResponse {
repeated string uris = 1;
}

// - Returns feed generator validity and online status with uris A, B, C…
// - Not currently being used, but could be worhthwhile.
message GetFeedGeneratorStatusRequest {
Expand Down Expand Up @@ -997,6 +1006,7 @@ service Service {
rpc GetActorFeeds(GetActorFeedsRequest) returns (GetActorFeedsResponse);
rpc GetSuggestedFeeds(GetSuggestedFeedsRequest) returns (GetSuggestedFeedsResponse);
rpc GetFeedGeneratorStatus(GetFeedGeneratorStatusRequest) returns (GetFeedGeneratorStatusResponse);
rpc SearchFeedGenerators(SearchFeedGeneratorsRequest) returns (SearchFeedGeneratorsResponse);

// Feeds
rpc GetAuthorFeed(GetAuthorFeedRequest) returns (GetAuthorFeedResponse);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,26 @@ export default function (server: Server, ctx: AppContext) {
}
}

const suggestedRes = await ctx.dataplane.getSuggestedFeeds({
actorDid: viewer ?? undefined,
limit: params.limit,
cursor: params.cursor,
})
const uris = suggestedRes.uris
let uris: string[]
let cursor: string | undefined

const query = params.query?.trim() ?? ''
if (query) {
const res = await ctx.dataplane.searchFeedGenerators({
query,
limit: params.limit,
})
uris = res.uris
} else {
const res = await ctx.dataplane.getSuggestedFeeds({
actorDid: viewer ?? undefined,
limit: params.limit,
cursor: params.cursor,
})
uris = res.uris
cursor = parseString(res.cursor)
}

const hydration = await ctx.hydrator.hydrateFeedGens(uris, viewer)
const feedViews = mapDefined(uris, (uri) =>
ctx.views.feedGenerator(uri, hydration),
Expand All @@ -35,7 +49,7 @@ export default function (server: Server, ctx: AppContext) {
encoding: 'application/json',
body: {
feeds: feedViews,
cursor: parseString(suggestedRes.cursor),
cursor,
},
}
},
Expand Down
25 changes: 24 additions & 1 deletion packages/bsky/src/data-plane/server/routes/feed-gens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,37 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
}
},

async getSuggestedFeeds() {
async getSuggestedFeeds(req) {
const feeds = await db.db
.selectFrom('suggested_feed')
.orderBy('suggested_feed.order', 'asc')
.if(!!req.cursor, (q) => q.where('order', '>', parseInt(req.cursor, 10)))
.limit(req.limit || 50)
.selectAll()
.execute()
return {
uris: feeds.map((f) => f.uri),
cursor: feeds.at(-1)?.order.toString(),
}
},

async searchFeedGenerators(req) {
const { ref } = db.db.dynamic
const limit = req.limit
const query = req.query.trim()
let builder = db.db
.selectFrom('feed_generator')
.if(!!query, (q) => q.where('displayName', 'ilike', `%${query}%`))
.selectAll()
const keyset = new TimeCidKeyset(
ref('feed_generator.createdAt'),
ref('feed_generator.cid'),
)
builder = paginate(builder, { limit, keyset })
const feeds = await builder.execute()
return {
uris: feeds.map((f) => f.uri),
cursor: keyset.packFromResult(feeds),
}
},

Expand Down
11 changes: 11 additions & 0 deletions packages/bsky/src/proto/bsky_connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ import {
PingResponse,
SearchActorsRequest,
SearchActorsResponse,
SearchFeedGeneratorsRequest,
SearchFeedGeneratorsResponse,
SearchPostsRequest,
SearchPostsResponse,
TakedownActorRequest,
Expand Down Expand Up @@ -620,6 +622,15 @@ export const Service = {
O: GetFeedGeneratorStatusResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc bsky.Service.SearchFeedGenerators
*/
searchFeedGenerators: {
name: 'SearchFeedGenerators',
I: SearchFeedGeneratorsRequest,
O: SearchFeedGeneratorsResponse,
kind: MethodKind.Unary,
},
/**
* Feeds
*
Expand Down
125 changes: 125 additions & 0 deletions packages/bsky/src/proto/bsky_pb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6546,6 +6546,131 @@ export class GetSuggestedFeedsResponse extends Message<GetSuggestedFeedsResponse
}
}

/**
* @generated from message bsky.SearchFeedGeneratorsRequest
*/
export class SearchFeedGeneratorsRequest extends Message<SearchFeedGeneratorsRequest> {
/**
* @generated from field: string query = 1;
*/
query = ''

/**
* @generated from field: int32 limit = 2;
*/
limit = 0

constructor(data?: PartialMessage<SearchFeedGeneratorsRequest>) {
super()
proto3.util.initPartial(data, this)
}

static readonly runtime: typeof proto3 = proto3
static readonly typeName = 'bsky.SearchFeedGeneratorsRequest'
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{ no: 1, name: 'query', kind: 'scalar', T: 9 /* ScalarType.STRING */ },
{ no: 2, name: 'limit', kind: 'scalar', T: 5 /* ScalarType.INT32 */ },
])

static fromBinary(
bytes: Uint8Array,
options?: Partial<BinaryReadOptions>,
): SearchFeedGeneratorsRequest {
return new SearchFeedGeneratorsRequest().fromBinary(bytes, options)
}

static fromJson(
jsonValue: JsonValue,
options?: Partial<JsonReadOptions>,
): SearchFeedGeneratorsRequest {
return new SearchFeedGeneratorsRequest().fromJson(jsonValue, options)
}

static fromJsonString(
jsonString: string,
options?: Partial<JsonReadOptions>,
): SearchFeedGeneratorsRequest {
return new SearchFeedGeneratorsRequest().fromJsonString(jsonString, options)
}

static equals(
a:
| SearchFeedGeneratorsRequest
| PlainMessage<SearchFeedGeneratorsRequest>
| undefined,
b:
| SearchFeedGeneratorsRequest
| PlainMessage<SearchFeedGeneratorsRequest>
| undefined,
): boolean {
return proto3.util.equals(SearchFeedGeneratorsRequest, a, b)
}
}

/**
* @generated from message bsky.SearchFeedGeneratorsResponse
*/
export class SearchFeedGeneratorsResponse extends Message<SearchFeedGeneratorsResponse> {
/**
* @generated from field: repeated string uris = 1;
*/
uris: string[] = []

constructor(data?: PartialMessage<SearchFeedGeneratorsResponse>) {
super()
proto3.util.initPartial(data, this)
}

static readonly runtime: typeof proto3 = proto3
static readonly typeName = 'bsky.SearchFeedGeneratorsResponse'
static readonly fields: FieldList = proto3.util.newFieldList(() => [
{
no: 1,
name: 'uris',
kind: 'scalar',
T: 9 /* ScalarType.STRING */,
repeated: true,
},
])

static fromBinary(
bytes: Uint8Array,
options?: Partial<BinaryReadOptions>,
): SearchFeedGeneratorsResponse {
return new SearchFeedGeneratorsResponse().fromBinary(bytes, options)
}

static fromJson(
jsonValue: JsonValue,
options?: Partial<JsonReadOptions>,
): SearchFeedGeneratorsResponse {
return new SearchFeedGeneratorsResponse().fromJson(jsonValue, options)
}

static fromJsonString(
jsonString: string,
options?: Partial<JsonReadOptions>,
): SearchFeedGeneratorsResponse {
return new SearchFeedGeneratorsResponse().fromJsonString(
jsonString,
options,
)
}

static equals(
a:
| SearchFeedGeneratorsResponse
| PlainMessage<SearchFeedGeneratorsResponse>
| undefined,
b:
| SearchFeedGeneratorsResponse
| PlainMessage<SearchFeedGeneratorsResponse>
| undefined,
): boolean {
return proto3.util.equals(SearchFeedGeneratorsResponse, a, b)
}
}

/**
* - Returns feed generator validity and online status with uris A, B, C…
* - Not currently being used, but could be worhthwhile.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1597,6 +1597,7 @@ Object {

exports[`feed generation getSuggestedFeeds returns list of suggested feed generators 1`] = `
Object {
"cursor": "4",
"feeds": Array [
Object {
"cid": "cids(0)",
Expand Down
29 changes: 18 additions & 11 deletions packages/bsky/tests/feed-generation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,18 +348,26 @@ describe('feed generation', () => {
})
})

// @TODO support from dataplane
describe.skip('getPopularFeedGenerators', () => {
describe('getPopularFeedGenerators', () => {
it('gets popular feed generators', async () => {
const resEven =
await agent.api.app.bsky.unspecced.getPopularFeedGenerators(
{},
{ headers: await network.serviceHeaders(sc.dids.bob) },
)
expect(resEven.data.feeds.map((f) => f.likeCount)).toEqual([
2, 0, 0, 0, 0,
const res = await agent.api.app.bsky.unspecced.getPopularFeedGenerators(
{},
{ headers: await network.serviceHeaders(sc.dids.bob) },
)
expect(res.data.feeds.map((f) => f.uri)).not.toContain(feedUriPrime) // taken-down
expect(res.data.feeds.map((f) => f.uri)).toEqual([
feedUriAll,
feedUriEven,
feedUriBadPagination,
])
expect(resEven.data.feeds.map((f) => f.uri)).not.toContain(feedUriPrime) // taken-down
})

it('searches feed generators', async () => {
const res = await agent.api.app.bsky.unspecced.getPopularFeedGenerators(
{ query: 'all' },
{ headers: await network.serviceHeaders(sc.dids.bob) },
)
expect(res.data.feeds.map((f) => f.uri)).toEqual([feedUriAll])
})

it('paginates', async () => {
Expand All @@ -368,7 +376,6 @@ describe('feed generation', () => {
{},
{ headers: await network.serviceHeaders(sc.dids.bob) },
)

const resOne =
await agent.api.app.bsky.unspecced.getPopularFeedGenerators(
{ limit: 2 },
Expand Down
Loading