@atproto/bsky 0.0.37 → 0.0.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/api/app/bsky/feed/getAuthorFeed.d.ts +2 -3
  3. package/dist/api/app/bsky/feed/getListFeed.d.ts +2 -2
  4. package/dist/api/app/bsky/feed/getTimeline.d.ts +4 -2
  5. package/dist/api/app/bsky/labeler/getServices.d.ts +3 -0
  6. package/dist/api/util.d.ts +9 -2
  7. package/dist/auth-verifier.d.ts +1 -1
  8. package/dist/context.d.ts +3 -0
  9. package/dist/data-plane/server/db/database-schema.d.ts +2 -2
  10. package/dist/data-plane/server/db/migrations/20240226T225725627Z-labelers.d.ts +3 -0
  11. package/dist/data-plane/server/db/migrations/index.d.ts +1 -0
  12. package/dist/data-plane/server/db/tables/labeler.d.ts +13 -0
  13. package/dist/data-plane/server/indexing/index.d.ts +2 -0
  14. package/dist/data-plane/server/indexing/plugins/labeler.d.ts +10 -0
  15. package/dist/data-plane/server/util.d.ts +6 -6
  16. package/dist/hydration/actor.d.ts +3 -0
  17. package/dist/hydration/hydrator.d.ts +27 -22
  18. package/dist/hydration/label.d.ts +23 -9
  19. package/dist/index.js +4068 -4641
  20. package/dist/index.js.map +3 -3
  21. package/dist/lexicon/index.d.ts +7 -27
  22. package/dist/lexicon/lexicons.d.ts +507 -1467
  23. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +23 -1
  24. package/dist/lexicon/types/app/bsky/embed/record.d.ts +2 -1
  25. package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -0
  26. package/dist/lexicon/types/app/bsky/graph/defs.d.ts +3 -0
  27. package/dist/lexicon/types/app/bsky/labeler/defs.d.ts +41 -0
  28. package/dist/lexicon/types/{com/atproto/admin/searchRepos.d.ts → app/bsky/labeler/getServices.d.ts} +7 -7
  29. package/dist/lexicon/types/app/bsky/labeler/service.d.ts +14 -0
  30. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +0 -304
  31. package/dist/lexicon/types/com/atproto/label/defs.d.ts +23 -0
  32. package/dist/proto/bsky_connect.d.ts +7 -1
  33. package/dist/proto/bsky_pb.d.ts +25 -0
  34. package/dist/util.d.ts +7 -0
  35. package/dist/views/index.d.ts +3 -0
  36. package/dist/views/types.d.ts +2 -1
  37. package/package.json +14 -13
  38. package/proto/bsky.proto +12 -0
  39. package/src/api/app/bsky/actor/getProfile.ts +21 -17
  40. package/src/api/app/bsky/actor/getProfiles.ts +16 -7
  41. package/src/api/app/bsky/actor/getSuggestions.ts +18 -13
  42. package/src/api/app/bsky/actor/searchActors.ts +9 -5
  43. package/src/api/app/bsky/actor/searchActorsTypeahead.ts +12 -5
  44. package/src/api/app/bsky/feed/getActorFeeds.ts +16 -6
  45. package/src/api/app/bsky/feed/getActorLikes.ts +18 -8
  46. package/src/api/app/bsky/feed/getAuthorFeed.ts +18 -19
  47. package/src/api/app/bsky/feed/getFeed.ts +14 -7
  48. package/src/api/app/bsky/feed/getFeedGenerator.ts +8 -2
  49. package/src/api/app/bsky/feed/getFeedGenerators.ts +16 -5
  50. package/src/api/app/bsky/feed/getLikes.ts +13 -6
  51. package/src/api/app/bsky/feed/getListFeed.ts +13 -7
  52. package/src/api/app/bsky/feed/getPostThread.ts +15 -8
  53. package/src/api/app/bsky/feed/getPosts.ts +14 -5
  54. package/src/api/app/bsky/feed/getRepostedBy.ts +13 -6
  55. package/src/api/app/bsky/feed/getSuggestedFeeds.ts +8 -2
  56. package/src/api/app/bsky/feed/getTimeline.ts +14 -8
  57. package/src/api/app/bsky/feed/searchPosts.ts +9 -5
  58. package/src/api/app/bsky/graph/getBlocks.ts +10 -9
  59. package/src/api/app/bsky/graph/getFollowers.ts +23 -15
  60. package/src/api/app/bsky/graph/getFollows.ts +23 -15
  61. package/src/api/app/bsky/graph/getList.ts +14 -8
  62. package/src/api/app/bsky/graph/getListBlocks.ts +10 -7
  63. package/src/api/app/bsky/graph/getListMutes.ts +10 -7
  64. package/src/api/app/bsky/graph/getLists.ts +9 -7
  65. package/src/api/app/bsky/graph/getMutes.ts +10 -8
  66. package/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts +10 -7
  67. package/src/api/app/bsky/graph/muteActor.ts +1 -1
  68. package/src/api/app/bsky/labeler/getServices.ts +46 -0
  69. package/src/api/app/bsky/notification/listNotifications.ts +12 -8
  70. package/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +6 -3
  71. package/src/api/com/atproto/admin/getAccountInfos.ts +10 -3
  72. package/src/api/index.ts +2 -0
  73. package/src/api/util.ts +19 -4
  74. package/src/auth-verifier.ts +2 -2
  75. package/src/context.ts +20 -0
  76. package/src/data-plane/server/db/database-schema.ts +4 -4
  77. package/src/data-plane/server/db/migrations/20240226T225725627Z-labelers.ts +27 -0
  78. package/src/data-plane/server/db/migrations/index.ts +1 -0
  79. package/src/data-plane/server/db/tables/labeler.ts +16 -0
  80. package/src/data-plane/server/indexing/index.ts +4 -0
  81. package/src/data-plane/server/indexing/plugins/labeler.ts +77 -0
  82. package/src/data-plane/server/routes/interactions.ts +17 -1
  83. package/src/data-plane/server/routes/profile.ts +15 -1
  84. package/src/data-plane/server/routes/records.ts +1 -0
  85. package/src/hydration/actor.ts +6 -0
  86. package/src/hydration/hydrator.ts +171 -97
  87. package/src/hydration/label.ts +106 -20
  88. package/src/index.ts +1 -3
  89. package/src/lexicon/index.ts +22 -137
  90. package/src/lexicon/lexicons.ts +502 -1598
  91. package/src/lexicon/types/app/bsky/actor/defs.ts +57 -1
  92. package/src/lexicon/types/app/bsky/embed/record.ts +2 -0
  93. package/src/lexicon/types/app/bsky/feed/defs.ts +1 -0
  94. package/src/lexicon/types/app/bsky/graph/defs.ts +3 -0
  95. package/src/lexicon/types/app/bsky/labeler/defs.ts +93 -0
  96. package/src/lexicon/types/{com/atproto/admin/searchRepos.ts → app/bsky/labeler/getServices.ts} +8 -8
  97. package/src/lexicon/types/app/bsky/labeler/service.ts +31 -0
  98. package/src/lexicon/types/com/atproto/admin/defs.ts +0 -694
  99. package/src/lexicon/types/com/atproto/label/defs.ts +78 -0
  100. package/src/proto/bsky_connect.ts +11 -0
  101. package/src/proto/bsky_pb.ts +146 -0
  102. package/src/util.ts +44 -0
  103. package/src/views/index.ts +76 -7
  104. package/src/views/types.ts +6 -3
  105. package/tests/__snapshots__/feed-generation.test.ts.snap +12 -0
  106. package/tests/_util.ts +21 -0
  107. package/tests/data-plane/__snapshots__/indexing.test.ts.snap +20 -0
  108. package/tests/label-hydration.test.ts +162 -0
  109. package/tests/views/__snapshots__/block-lists.test.ts.snap +7 -0
  110. package/tests/views/__snapshots__/labeler-service.test.ts.snap +160 -0
  111. package/tests/views/__snapshots__/mute-lists.test.ts.snap +10 -0
  112. package/tests/views/__snapshots__/profile.test.ts.snap +40 -0
  113. package/tests/views/__snapshots__/threadgating.test.ts.snap +2 -0
  114. package/tests/views/labeler-service.test.ts +156 -0
  115. package/tests/views/takedown-labels.test.ts +133 -0
  116. package/tests/views/timeline.test.ts +7 -2
  117. package/dist/data-plane/server/db/tables/moderation.d.ts +0 -42
  118. package/dist/lexicon/types/com/atproto/admin/createCommunicationTemplate.d.ts +0 -37
  119. package/dist/lexicon/types/com/atproto/admin/deleteCommunicationTemplate.d.ts +0 -25
  120. package/dist/lexicon/types/com/atproto/admin/emitModerationEvent.d.ts +0 -45
  121. package/dist/lexicon/types/com/atproto/admin/getModerationEvent.d.ts +0 -29
  122. package/dist/lexicon/types/com/atproto/admin/getRecord.d.ts +0 -31
  123. package/dist/lexicon/types/com/atproto/admin/getRepo.d.ts +0 -30
  124. package/dist/lexicon/types/com/atproto/admin/listCommunicationTemplates.d.ts +0 -31
  125. package/dist/lexicon/types/com/atproto/admin/queryModerationEvents.d.ts +0 -48
  126. package/dist/lexicon/types/com/atproto/admin/queryModerationStatuses.d.ts +0 -50
  127. package/dist/lexicon/types/com/atproto/admin/updateCommunicationTemplate.d.ts +0 -39
  128. package/src/data-plane/server/db/tables/moderation.ts +0 -59
  129. package/src/lexicon/types/com/atproto/admin/createCommunicationTemplate.ts +0 -54
  130. package/src/lexicon/types/com/atproto/admin/deleteCommunicationTemplate.ts +0 -38
  131. package/src/lexicon/types/com/atproto/admin/emitModerationEvent.ts +0 -67
  132. package/src/lexicon/types/com/atproto/admin/getModerationEvent.ts +0 -41
  133. package/src/lexicon/types/com/atproto/admin/getRecord.ts +0 -43
  134. package/src/lexicon/types/com/atproto/admin/getRepo.ts +0 -42
  135. package/src/lexicon/types/com/atproto/admin/listCommunicationTemplates.ts +0 -44
  136. package/src/lexicon/types/com/atproto/admin/queryModerationEvents.ts +0 -73
  137. package/src/lexicon/types/com/atproto/admin/queryModerationStatuses.ts +0 -74
  138. package/src/lexicon/types/com/atproto/admin/updateCommunicationTemplate.ts +0 -57
@@ -2,7 +2,7 @@ import { mapDefined } from '@atproto/common'
2
2
  import { Server } from '../../../../lexicon'
3
3
  import AppContext from '../../../../context'
4
4
  import { parseString } from '../../../../hydration/util'
5
- import { clearlyBadCursor } from '../../../util'
5
+ import { clearlyBadCursor, resHeaders } from '../../../util'
6
6
 
7
7
  // THIS IS A TEMPORARY UNSPECCED ROUTE
8
8
  // @TODO currently mirrors getSuggestedFeeds and ignores the "query" param.
@@ -10,8 +10,10 @@ import { clearlyBadCursor } from '../../../util'
10
10
  export default function (server: Server, ctx: AppContext) {
11
11
  server.app.bsky.unspecced.getPopularFeedGenerators({
12
12
  auth: ctx.authVerifier.standardOptional,
13
- handler: async ({ auth, params }) => {
13
+ handler: async ({ auth, params, req }) => {
14
14
  const viewer = auth.credentials.iss
15
+ const labelers = ctx.reqLabelers(req)
16
+ const hydrateCtx = { viewer, labelers }
15
17
 
16
18
  if (clearlyBadCursor(params.cursor)) {
17
19
  return {
@@ -40,7 +42,7 @@ export default function (server: Server, ctx: AppContext) {
40
42
  cursor = parseString(res.cursor)
41
43
  }
42
44
 
43
- const hydration = await ctx.hydrator.hydrateFeedGens(uris, viewer)
45
+ const hydration = await ctx.hydrator.hydrateFeedGens(uris, hydrateCtx)
44
46
  const feedViews = mapDefined(uris, (uri) =>
45
47
  ctx.views.feedGenerator(uri, hydration),
46
48
  )
@@ -51,6 +53,7 @@ export default function (server: Server, ctx: AppContext) {
51
53
  feeds: feedViews,
52
54
  cursor,
53
55
  },
56
+ headers: resHeaders({ labelers }),
54
57
  }
55
58
  },
56
59
  })
@@ -5,18 +5,25 @@ import { INVALID_HANDLE } from '@atproto/syntax'
5
5
 
6
6
  export default function (server: Server, ctx: AppContext) {
7
7
  server.com.atproto.admin.getAccountInfos({
8
- auth: ctx.authVerifier.roleOrModService,
9
- handler: async ({ params }) => {
8
+ auth: ctx.authVerifier.optionalStandardOrRole,
9
+ handler: async ({ params, auth }) => {
10
10
  const { dids } = params
11
+ const { includeTakedowns } = ctx.authVerifier.parseCreds(auth)
12
+
11
13
  const actors = await ctx.hydrator.actor.getActors(dids, true)
12
14
 
13
15
  const infos = mapDefined(dids, (did) => {
14
16
  const info = actors.get(did)
15
17
  if (!info) return
18
+ if (info.takedownRef && !includeTakedowns) return
19
+ const profileRecord =
20
+ !info.profileTakedownRef || includeTakedowns
21
+ ? info.profile
22
+ : undefined
16
23
  return {
17
24
  did,
18
25
  handle: info.handle ?? INVALID_HANDLE,
19
- relatedRecords: info.profile ? [info.profile] : undefined,
26
+ relatedRecords: profileRecord ? [profileRecord] : undefined,
20
27
  indexedAt: (info.sortedAt ?? new Date(0)).toISOString(),
21
28
  }
22
29
  })
package/src/api/index.ts CHANGED
@@ -30,6 +30,7 @@ import unmuteActor from './app/bsky/graph/unmuteActor'
30
30
  import muteActorList from './app/bsky/graph/muteActorList'
31
31
  import unmuteActorList from './app/bsky/graph/unmuteActorList'
32
32
  import getSuggestedFollowsByActor from './app/bsky/graph/getSuggestedFollowsByActor'
33
+ import getLabelerServices from './app/bsky/labeler/getServices'
33
34
  import searchActors from './app/bsky/actor/searchActors'
34
35
  import searchActorsTypeahead from './app/bsky/actor/searchActorsTypeahead'
35
36
  import getSuggestions from './app/bsky/actor/getSuggestions'
@@ -84,6 +85,7 @@ export default function (server: Server, ctx: AppContext) {
84
85
  muteActorList(server, ctx)
85
86
  unmuteActorList(server, ctx)
86
87
  getSuggestedFollowsByActor(server, ctx)
88
+ getLabelerServices(server, ctx)
87
89
  searchActors(server, ctx)
88
90
  searchActorsTypeahead(server, ctx)
89
91
  getSuggestions(server, ctx)
package/src/api/util.ts CHANGED
@@ -1,9 +1,24 @@
1
- import express from 'express'
1
+ import { ParsedLabelers, formatLabelerHeader } from '../util'
2
2
 
3
- export const setRepoRev = (res: express.Response, rev: string | null) => {
4
- if (rev !== null) {
5
- res.setHeader('Atproto-Repo-Rev', rev)
3
+ export const ATPROTO_CONTENT_LABELERS = 'Atproto-Content-Labelers'
4
+ export const ATPROTO_REPO_REV = 'Atproto-Repo-Rev'
5
+
6
+ type ResHeaderOpts = {
7
+ labelers: ParsedLabelers
8
+ repoRev: string | null
9
+ }
10
+
11
+ export const resHeaders = (
12
+ opts: Partial<ResHeaderOpts>,
13
+ ): Record<string, string> => {
14
+ const headers = {}
15
+ if (opts.labelers) {
16
+ headers[ATPROTO_CONTENT_LABELERS] = formatLabelerHeader(opts.labelers)
17
+ }
18
+ if (opts.repoRev) {
19
+ headers[ATPROTO_REPO_REV] = opts.repoRev
6
20
  }
21
+ return headers
7
22
  }
8
23
 
9
24
  export const clearlyBadCursor = (cursor?: string) => {
@@ -258,7 +258,7 @@ export class AuthVerifier {
258
258
  ) {
259
259
  const viewer =
260
260
  creds.credentials.type === 'standard' ? creds.credentials.iss : null
261
- const canViewTakedowns =
261
+ const includeTakedowns =
262
262
  (creds.credentials.type === 'role' && creds.credentials.admin) ||
263
263
  creds.credentials.type === 'mod_service' ||
264
264
  (creds.credentials.type === 'standard' &&
@@ -269,7 +269,7 @@ export class AuthVerifier {
269
269
 
270
270
  return {
271
271
  viewer,
272
- canViewTakedowns,
272
+ includeTakedowns,
273
273
  canPerformTakedown,
274
274
  }
275
275
  }
package/src/context.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import express from 'express'
1
2
  import * as plc from '@did-plc/lib'
2
3
  import { IdResolver } from '@atproto/identity'
3
4
  import AtpAgent from '@atproto/api'
@@ -10,6 +11,12 @@ import { Views } from './views'
10
11
  import { AuthVerifier } from './auth-verifier'
11
12
  import { BsyncClient } from './bsync'
12
13
  import { CourierClient } from './courier'
14
+ import {
15
+ ParsedLabelers,
16
+ defaultLabelerHeader,
17
+ parseLabelerHeader,
18
+ } from './util'
19
+ import { httpLogger as log } from './logger'
13
20
 
14
21
  export class AppContext {
15
22
  constructor(
@@ -79,6 +86,19 @@ export class AppContext {
79
86
  keypair: this.signingKey,
80
87
  })
81
88
  }
89
+
90
+ reqLabelers(req: express.Request): ParsedLabelers {
91
+ const val = req.header('atproto-accept-labelers')
92
+ let parsed: ParsedLabelers | null
93
+ try {
94
+ parsed = parseLabelerHeader(val)
95
+ } catch (err) {
96
+ parsed = null
97
+ log.info({ err, val }, 'failed to parse labeler header')
98
+ }
99
+ if (!parsed) return defaultLabelerHeader(this.cfg.labelsFromIssuerDids)
100
+ return parsed
101
+ }
82
102
  }
83
103
 
84
104
  export default AppContext
@@ -25,7 +25,6 @@ import * as record from './tables/record'
25
25
  import * as notification from './tables/notification'
26
26
  import * as notificationPushToken from './tables/notification-push-token'
27
27
  import * as didCache from './tables/did-cache'
28
- import * as moderation from './tables/moderation'
29
28
  import * as label from './tables/label'
30
29
  import * as algo from './tables/algo'
31
30
  import * as viewParam from './tables/view-param'
@@ -33,6 +32,7 @@ import * as suggestedFollow from './tables/suggested-follow'
33
32
  import * as suggestedFeed from './tables/suggested-feed'
34
33
  import * as taggedSuggestion from './tables/tagged-suggestion'
35
34
  import * as blobTakedown from './tables/blob-takedown'
35
+ import * as labeler from './tables/labeler'
36
36
 
37
37
  export type DatabaseSchemaType = duplicateRecord.PartialDB &
38
38
  profile.PartialDB &
@@ -60,14 +60,14 @@ export type DatabaseSchemaType = duplicateRecord.PartialDB &
60
60
  notification.PartialDB &
61
61
  notificationPushToken.PartialDB &
62
62
  didCache.PartialDB &
63
- moderation.PartialDB &
64
63
  label.PartialDB &
65
64
  algo.PartialDB &
66
65
  viewParam.PartialDB &
67
66
  suggestedFollow.PartialDB &
68
67
  suggestedFeed.PartialDB &
69
- taggedSuggestion.PartialDB &
70
- blobTakedown.PartialDB
68
+ blobTakedown.PartialDB &
69
+ labeler.PartialDB &
70
+ taggedSuggestion.PartialDB
71
71
 
72
72
  export type DatabaseSchema = Kysely<DatabaseSchemaType>
73
73
 
@@ -0,0 +1,27 @@
1
+ import { Kysely, sql } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ await db.schema
5
+ .createTable('labeler')
6
+ .addColumn('uri', 'varchar', (col) => col.primaryKey())
7
+ .addColumn('cid', 'varchar', (col) => col.notNull())
8
+ .addColumn('creator', 'varchar', (col) => col.notNull())
9
+ .addColumn('createdAt', 'varchar', (col) => col.notNull())
10
+ .addColumn('indexedAt', 'varchar', (col) => col.notNull())
11
+ .addColumn('sortAt', 'varchar', (col) =>
12
+ col
13
+ .generatedAlwaysAs(sql`least("createdAt", "indexedAt")`)
14
+ .stored()
15
+ .notNull(),
16
+ )
17
+ .execute()
18
+ await db.schema
19
+ .createIndex('labeler_order_by_idx')
20
+ .on('labeler')
21
+ .columns(['sortAt', 'cid'])
22
+ .execute()
23
+ }
24
+
25
+ export async function down(db: Kysely<unknown>): Promise<void> {
26
+ await db.schema.dropTable('labeler').execute()
27
+ }
@@ -33,3 +33,4 @@ export * as _20230929T192920807Z from './20230929T192920807Z-record-cursor-index
33
33
  export * as _20231003T202833377Z from './20231003T202833377Z-create-moderation-subject-status'
34
34
  export * as _20231220T225126090Z from './20231220T225126090Z-blob-takedowns'
35
35
  export * as _20240124T023719200Z from './20240124T023719200Z-tagged-suggestions'
36
+ export * as _20240226T225725627Z from './20240226T225725627Z-labelers'
@@ -0,0 +1,16 @@
1
+ import { GeneratedAlways } from 'kysely'
2
+
3
+ export const tableName = 'labeler'
4
+
5
+ export interface Labeler {
6
+ uri: string
7
+ cid: string
8
+ creator: string
9
+ createdAt: string
10
+ indexedAt: string
11
+ sortAt: GeneratedAlways<string>
12
+ }
13
+
14
+ export type PartialDB = {
15
+ [tableName]: Labeler
16
+ }
@@ -26,6 +26,7 @@ import * as ListItem from './plugins/list-item'
26
26
  import * as ListBlock from './plugins/list-block'
27
27
  import * as Block from './plugins/block'
28
28
  import * as FeedGenerator from './plugins/feed-generator'
29
+ import * as Labeler from './plugins/labeler'
29
30
  import RecordProcessor from './processor'
30
31
  import { subLogger } from '../../../logger'
31
32
  import { retryHttp } from '../../../util/retry'
@@ -44,6 +45,7 @@ export class IndexingService {
44
45
  listBlock: ListBlock.PluginType
45
46
  block: Block.PluginType
46
47
  feedGenerator: FeedGenerator.PluginType
48
+ labeler: Labeler.PluginType
47
49
  }
48
50
 
49
51
  constructor(
@@ -63,6 +65,7 @@ export class IndexingService {
63
65
  listBlock: ListBlock.makePlugin(this.db, this.background),
64
66
  block: Block.makePlugin(this.db, this.background),
65
67
  feedGenerator: FeedGenerator.makePlugin(this.db, this.background),
68
+ labeler: Labeler.makePlugin(this.db, this.background),
66
69
  }
67
70
  }
68
71
 
@@ -298,6 +301,7 @@ export class IndexingService {
298
301
  .deleteFrom('feed_generator')
299
302
  .where('creator', '=', did)
300
303
  .execute()
304
+ await this.db.db.deleteFrom('labeler').where('creator', '=', did).execute()
301
305
  // lists
302
306
  await this.db.db
303
307
  .deleteFrom('list_item')
@@ -0,0 +1,77 @@
1
+ import { Selectable } from 'kysely'
2
+ import { AtUri, normalizeDatetimeAlways } from '@atproto/syntax'
3
+ import { CID } from 'multiformats/cid'
4
+ import * as Labeler from '../../../../lexicon/types/app/bsky/labeler/service'
5
+ import * as lex from '../../../../lexicon/lexicons'
6
+ import { Database } from '../../db'
7
+ import { DatabaseSchema, DatabaseSchemaType } from '../../db/database-schema'
8
+ import RecordProcessor from '../processor'
9
+ import { BackgroundQueue } from '../../background'
10
+
11
+ const lexId = lex.ids.AppBskyLabelerService
12
+ type IndexedLabeler = Selectable<DatabaseSchemaType['labeler']>
13
+
14
+ const insertFn = async (
15
+ db: DatabaseSchema,
16
+ uri: AtUri,
17
+ cid: CID,
18
+ obj: Labeler.Record,
19
+ timestamp: string,
20
+ ): Promise<IndexedLabeler | null> => {
21
+ if (uri.rkey !== 'self') return null
22
+ const inserted = await db
23
+ .insertInto('labeler')
24
+ .values({
25
+ uri: uri.toString(),
26
+ cid: cid.toString(),
27
+ creator: uri.host,
28
+ createdAt: normalizeDatetimeAlways(obj.createdAt),
29
+ indexedAt: timestamp,
30
+ })
31
+ .onConflict((oc) => oc.doNothing())
32
+ .returningAll()
33
+ .executeTakeFirst()
34
+ return inserted || null
35
+ }
36
+
37
+ const findDuplicate = async (): Promise<AtUri | null> => {
38
+ return null
39
+ }
40
+
41
+ const notifsForInsert = () => {
42
+ return []
43
+ }
44
+
45
+ const deleteFn = async (
46
+ db: DatabaseSchema,
47
+ uri: AtUri,
48
+ ): Promise<IndexedLabeler | null> => {
49
+ const deleted = await db
50
+ .deleteFrom('labeler')
51
+ .where('uri', '=', uri.toString())
52
+ .returningAll()
53
+ .executeTakeFirst()
54
+ return deleted || null
55
+ }
56
+
57
+ const notifsForDelete = () => {
58
+ return { notifs: [], toDelete: [] }
59
+ }
60
+
61
+ export type PluginType = RecordProcessor<Labeler.Record, IndexedLabeler>
62
+
63
+ export const makePlugin = (
64
+ db: Database,
65
+ background: BackgroundQueue,
66
+ ): PluginType => {
67
+ return new RecordProcessor(db, background, {
68
+ lexId,
69
+ insertFn,
70
+ findDuplicate,
71
+ deleteFn,
72
+ notifsForInsert,
73
+ notifsForDelete,
74
+ })
75
+ }
76
+
77
+ export default makePlugin
@@ -2,6 +2,7 @@ import { keyBy } from '@atproto/common'
2
2
  import { ServiceImpl } from '@connectrpc/connect'
3
3
  import { Service } from '../../../proto/bsky_connect'
4
4
  import { Database } from '../db'
5
+ import { countAll } from '../db/util'
5
6
 
6
7
  export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
7
8
  async getInteractionCounts(req) {
@@ -25,16 +26,31 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
25
26
  if (req.dids.length === 0) {
26
27
  return { followers: [], following: [], posts: [] }
27
28
  }
29
+ const { ref } = db.db.dynamic
28
30
  const res = await db.db
29
31
  .selectFrom('profile_agg')
30
- .selectAll()
31
32
  .where('did', 'in', req.dids)
33
+ .selectAll('profile_agg')
34
+ .select([
35
+ db.db
36
+ .selectFrom('feed_generator')
37
+ .whereRef('creator', '=', ref('profile_agg.did'))
38
+ .select(countAll.as('val'))
39
+ .as('feedGensCount'),
40
+ db.db
41
+ .selectFrom('list')
42
+ .whereRef('creator', '=', ref('profile_agg.did'))
43
+ .select(countAll.as('val'))
44
+ .as('listsCount'),
45
+ ])
32
46
  .execute()
33
47
  const byDid = keyBy(res, 'did')
34
48
  return {
35
49
  followers: req.dids.map((uri) => byDid[uri]?.followersCount ?? 0),
36
50
  following: req.dids.map((uri) => byDid[uri]?.followsCount ?? 0),
37
51
  posts: req.dids.map((uri) => byDid[uri]?.postsCount ?? 0),
52
+ lists: req.dids.map((uri) => byDid[uri]?.listsCount ?? 0),
53
+ feeds: req.dids.map((uri) => byDid[uri]?.feedGensCount ?? 0),
38
54
  }
39
55
  },
40
56
  })
@@ -3,6 +3,7 @@ import { Service } from '../../../proto/bsky_connect'
3
3
  import { keyBy } from '@atproto/common'
4
4
  import { getRecords } from './records'
5
5
  import { Database } from '../db'
6
+ import { sql } from 'kysely'
6
7
 
7
8
  export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
8
9
  async getActors(req) {
@@ -13,8 +14,20 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
13
14
  const profileUris = dids.map(
14
15
  (did) => `at://${did}/app.bsky.actor.profile/self`,
15
16
  )
17
+ const { ref } = db.db.dynamic
16
18
  const [handlesRes, profiles] = await Promise.all([
17
- db.db.selectFrom('actor').where('did', 'in', dids).selectAll().execute(),
19
+ db.db
20
+ .selectFrom('actor')
21
+ .where('did', 'in', dids)
22
+ .selectAll('actor')
23
+ .select([
24
+ db.db
25
+ .selectFrom('labeler')
26
+ .whereRef('creator', '=', ref('actor.did'))
27
+ .select(sql<true>`${true}`.as('val'))
28
+ .as('isLabeler'),
29
+ ])
30
+ .execute(),
18
31
  getRecords(db)({ uris: profileUris }),
19
32
  ])
20
33
  const byDid = keyBy(handlesRes, 'did')
@@ -27,6 +40,7 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
27
40
  takenDown: !!row?.takedownRef,
28
41
  takedownRef: row?.takedownRef || undefined,
29
42
  tombstonedAt: undefined, // in current implementation, tombstoned actors are deleted
43
+ labeler: row?.isLabeler ?? false,
30
44
  }
31
45
  })
32
46
  return { actors }
@@ -20,6 +20,7 @@ export default (db: Database): Partial<ServiceImpl<typeof Service>> => ({
20
20
  getProfileRecords: getRecords(db, ids.AppBskyActorProfile),
21
21
  getRepostRecords: getRecords(db, ids.AppBskyFeedRepost),
22
22
  getThreadGateRecords: getRecords(db, ids.AppBskyFeedThreadgate),
23
+ getLabelerRecords: getRecords(db, ids.AppBskyLabelerService),
23
24
  })
24
25
 
25
26
  export const getRecords =
@@ -15,6 +15,7 @@ export type Actor = {
15
15
  profileTakedownRef?: string
16
16
  sortedAt?: Date
17
17
  takedownRef?: string
18
+ isLabeler: boolean
18
19
  }
19
20
 
20
21
  export type Actors = HydrationMap<Actor>
@@ -36,6 +37,8 @@ export type ProfileAgg = {
36
37
  followers: number
37
38
  follows: number
38
39
  posts: number
40
+ lists: number
41
+ feeds: number
39
42
  }
40
43
 
41
44
  export type ProfileAggs = HydrationMap<ProfileAgg>
@@ -100,6 +103,7 @@ export class ActorHydrator {
100
103
  profileTakedownRef: safeTakedownRef(profile),
101
104
  sortedAt: profile?.sortedAt?.toDate(),
102
105
  takedownRef: safeTakedownRef(actor),
106
+ isLabeler: actor.labeler ?? false,
103
107
  })
104
108
  }, new HydrationMap<Actor>())
105
109
  }
@@ -143,6 +147,8 @@ export class ActorHydrator {
143
147
  followers: counts.followers[i] ?? 0,
144
148
  follows: counts.following[i] ?? 0,
145
149
  posts: counts.posts[i] ?? 0,
150
+ lists: counts.lists[i] ?? 0,
151
+ feeds: counts.feeds[i] ?? 0,
146
152
  })
147
153
  }, new HydrationMap<ProfileAgg>())
148
154
  }