@atproto/bsky 0.0.15 → 0.0.17

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 (236) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/api/com/atproto/moderation/util.d.ts +4 -3
  3. package/dist/cache/read-through.d.ts +30 -0
  4. package/dist/config.d.ts +18 -0
  5. package/dist/context.d.ts +21 -6
  6. package/dist/daemon/config.d.ts +15 -0
  7. package/dist/daemon/context.d.ts +15 -0
  8. package/dist/daemon/index.d.ts +23 -0
  9. package/dist/daemon/logger.d.ts +3 -0
  10. package/dist/daemon/notifications.d.ts +18 -0
  11. package/dist/daemon/services.d.ts +11 -0
  12. package/dist/db/database-schema.d.ts +1 -2
  13. package/dist/db/index.js +41 -1
  14. package/dist/db/index.js.map +3 -3
  15. package/dist/db/migrations/20231003T202833377Z-create-moderation-subject-status.d.ts +3 -0
  16. package/dist/db/migrations/20231205T000257238Z-remove-did-cache.d.ts +3 -0
  17. package/dist/db/migrations/index.d.ts +2 -0
  18. package/dist/db/pagination.d.ts +2 -1
  19. package/dist/db/{periodic-moderation-action-reversal.d.ts → periodic-moderation-event-reversal.d.ts} +3 -5
  20. package/dist/db/tables/moderation.d.ts +24 -34
  21. package/dist/did-cache.d.ts +10 -7
  22. package/dist/feed-gen/types.d.ts +1 -1
  23. package/dist/index.d.ts +6 -1
  24. package/dist/index.js +4370 -2758
  25. package/dist/index.js.map +3 -3
  26. package/dist/indexer/context.d.ts +2 -0
  27. package/dist/indexer/index.d.ts +1 -0
  28. package/dist/lexicon/index.d.ts +23 -18
  29. package/dist/lexicon/lexicons.d.ts +561 -412
  30. package/dist/lexicon/types/app/bsky/feed/defs.d.ts +1 -7
  31. package/dist/lexicon/types/app/bsky/graph/defs.d.ts +1 -0
  32. package/dist/lexicon/types/com/atproto/admin/defs.d.ts +114 -48
  33. package/dist/lexicon/types/com/atproto/admin/{resolveModerationReports.d.ts → deleteAccount.d.ts} +2 -13
  34. package/dist/lexicon/types/com/atproto/admin/{takeModerationAction.d.ts → emitModerationEvent.d.ts} +5 -6
  35. package/dist/lexicon/types/com/atproto/admin/{getModerationAction.d.ts → getModerationEvent.d.ts} +1 -1
  36. package/dist/lexicon/types/com/atproto/admin/{getModerationActions.d.ts → queryModerationEvents.d.ts} +5 -1
  37. package/dist/lexicon/types/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +12 -6
  38. package/dist/lexicon/types/com/atproto/admin/sendEmail.d.ts +1 -0
  39. package/dist/lexicon/types/com/atproto/{admin/getModerationReport.d.ts → temp/importRepo.d.ts} +10 -7
  40. package/dist/lexicon/types/com/atproto/temp/pushBlob.d.ts +25 -0
  41. package/dist/lexicon/types/com/atproto/{admin/reverseModerationAction.d.ts → temp/transferAccount.d.ts} +11 -5
  42. package/dist/logger.d.ts +1 -0
  43. package/dist/migrate-moderation-data.d.ts +1 -0
  44. package/dist/redis.d.ts +10 -1
  45. package/dist/services/actor/index.d.ts +18 -4
  46. package/dist/services/actor/views.d.ts +6 -8
  47. package/dist/services/feed/index.d.ts +7 -4
  48. package/dist/services/feed/util.d.ts +9 -1
  49. package/dist/services/feed/views.d.ts +11 -21
  50. package/dist/services/graph/index.d.ts +5 -29
  51. package/dist/services/graph/types.d.ts +1 -0
  52. package/dist/services/index.d.ts +3 -7
  53. package/dist/services/label/index.d.ts +10 -4
  54. package/dist/services/moderation/index.d.ts +134 -72
  55. package/dist/services/moderation/pagination.d.ts +36 -0
  56. package/dist/services/moderation/status.d.ts +13 -0
  57. package/dist/services/moderation/types.d.ts +35 -0
  58. package/dist/services/moderation/views.d.ts +18 -14
  59. package/dist/services/types.d.ts +3 -0
  60. package/dist/services/util/notification.d.ts +5 -0
  61. package/dist/services/util/post.d.ts +6 -6
  62. package/dist/util/debug.d.ts +1 -1
  63. package/dist/util/retry.d.ts +1 -6
  64. package/package.json +11 -11
  65. package/src/api/app/bsky/feed/getActorFeeds.ts +2 -1
  66. package/src/api/app/bsky/feed/getActorLikes.ts +1 -3
  67. package/src/api/app/bsky/feed/getAuthorFeed.ts +1 -3
  68. package/src/api/app/bsky/feed/getFeed.ts +9 -9
  69. package/src/api/app/bsky/feed/getFeedGenerator.ts +3 -0
  70. package/src/api/app/bsky/feed/getFeedGenerators.ts +2 -1
  71. package/src/api/app/bsky/feed/getListFeed.ts +1 -3
  72. package/src/api/app/bsky/feed/getPostThread.ts +15 -54
  73. package/src/api/app/bsky/feed/getPosts.ts +21 -18
  74. package/src/api/app/bsky/feed/getSuggestedFeeds.ts +2 -1
  75. package/src/api/app/bsky/feed/getTimeline.ts +1 -3
  76. package/src/api/app/bsky/feed/searchPosts.ts +20 -17
  77. package/src/api/app/bsky/graph/getList.ts +6 -3
  78. package/src/api/app/bsky/graph/getListBlocks.ts +3 -2
  79. package/src/api/app/bsky/graph/getListMutes.ts +2 -1
  80. package/src/api/app/bsky/graph/getLists.ts +2 -1
  81. package/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +3 -1
  82. package/src/api/blob-resolver.ts +6 -11
  83. package/src/api/com/atproto/admin/emitModerationEvent.ts +220 -0
  84. package/src/api/com/atproto/admin/{getModerationActions.ts → getModerationEvent.ts} +5 -11
  85. package/src/api/com/atproto/admin/getRecord.ts +1 -0
  86. package/src/api/com/atproto/admin/{getModerationReports.ts → queryModerationEvents.ts} +13 -16
  87. package/src/api/com/atproto/admin/queryModerationStatuses.ts +55 -0
  88. package/src/api/com/atproto/moderation/createReport.ts +9 -7
  89. package/src/api/com/atproto/moderation/util.ts +38 -20
  90. package/src/api/index.ts +8 -14
  91. package/src/auth.ts +29 -21
  92. package/src/auto-moderator/index.ts +26 -19
  93. package/src/cache/read-through.ts +151 -0
  94. package/src/config.ts +90 -1
  95. package/src/context.ts +11 -7
  96. package/src/daemon/config.ts +60 -0
  97. package/src/daemon/context.ts +27 -0
  98. package/src/daemon/index.ts +78 -0
  99. package/src/daemon/logger.ts +6 -0
  100. package/src/daemon/notifications.ts +54 -0
  101. package/src/daemon/services.ts +22 -0
  102. package/src/db/database-schema.ts +0 -2
  103. package/src/db/migrations/20231003T202833377Z-create-moderation-subject-status.ts +123 -0
  104. package/src/db/migrations/20231205T000257238Z-remove-did-cache.ts +14 -0
  105. package/src/db/migrations/index.ts +2 -0
  106. package/src/db/pagination.ts +26 -3
  107. package/src/db/{periodic-moderation-action-reversal.ts → periodic-moderation-event-reversal.ts} +50 -46
  108. package/src/db/tables/moderation.ts +35 -52
  109. package/src/did-cache.ts +33 -56
  110. package/src/feed-gen/bsky-team.ts +1 -1
  111. package/src/feed-gen/hot-classic.ts +1 -1
  112. package/src/feed-gen/index.ts +0 -4
  113. package/src/feed-gen/mutuals.ts +6 -2
  114. package/src/feed-gen/types.ts +1 -1
  115. package/src/index.ts +57 -17
  116. package/src/indexer/context.ts +5 -0
  117. package/src/indexer/index.ts +10 -7
  118. package/src/lexicon/index.ts +80 -67
  119. package/src/lexicon/lexicons.ts +698 -507
  120. package/src/lexicon/types/app/bsky/feed/defs.ts +1 -18
  121. package/src/lexicon/types/app/bsky/graph/defs.ts +1 -0
  122. package/src/lexicon/types/com/atproto/admin/defs.ts +276 -84
  123. package/src/lexicon/types/com/atproto/admin/{resolveModerationReports.ts → deleteAccount.ts} +2 -13
  124. package/src/lexicon/types/com/atproto/admin/{takeModerationAction.ts → emitModerationEvent.ts} +13 -11
  125. package/src/lexicon/types/com/atproto/admin/{getModerationReport.ts → getModerationEvent.ts} +1 -1
  126. package/src/lexicon/types/com/atproto/admin/{getModerationActions.ts → queryModerationEvents.ts} +8 -1
  127. package/src/lexicon/types/com/atproto/admin/{getModerationReports.ts → queryModerationStatuses.ts} +21 -14
  128. package/src/lexicon/types/com/atproto/admin/sendEmail.ts +1 -0
  129. package/src/lexicon/types/com/atproto/{admin/getModerationAction.ts → temp/importRepo.ts} +11 -7
  130. package/src/lexicon/types/com/atproto/temp/pushBlob.ts +39 -0
  131. package/src/lexicon/types/com/atproto/{admin/reverseModerationAction.ts → temp/transferAccount.ts} +18 -5
  132. package/src/logger.ts +2 -0
  133. package/src/migrate-moderation-data.ts +414 -0
  134. package/src/redis.ts +43 -3
  135. package/src/services/actor/index.ts +55 -7
  136. package/src/services/actor/views.ts +18 -21
  137. package/src/services/feed/index.ts +52 -19
  138. package/src/services/feed/util.ts +47 -19
  139. package/src/services/feed/views.ts +87 -13
  140. package/src/services/graph/index.ts +21 -3
  141. package/src/services/graph/types.ts +1 -0
  142. package/src/services/index.ts +14 -14
  143. package/src/services/indexing/index.ts +7 -10
  144. package/src/services/indexing/plugins/block.ts +2 -3
  145. package/src/services/indexing/plugins/feed-generator.ts +2 -3
  146. package/src/services/indexing/plugins/follow.ts +2 -3
  147. package/src/services/indexing/plugins/like.ts +2 -3
  148. package/src/services/indexing/plugins/list-block.ts +2 -3
  149. package/src/services/indexing/plugins/list-item.ts +2 -3
  150. package/src/services/indexing/plugins/list.ts +2 -3
  151. package/src/services/indexing/plugins/post.ts +16 -4
  152. package/src/services/indexing/plugins/repost.ts +2 -3
  153. package/src/services/indexing/plugins/thread-gate.ts +2 -3
  154. package/src/services/label/index.ts +68 -25
  155. package/src/services/moderation/index.ts +380 -395
  156. package/src/services/moderation/pagination.ts +96 -0
  157. package/src/services/moderation/status.ts +241 -0
  158. package/src/services/moderation/types.ts +49 -0
  159. package/src/services/moderation/views.ts +278 -329
  160. package/src/services/types.ts +4 -0
  161. package/src/services/util/notification.ts +70 -0
  162. package/src/util/debug.ts +2 -2
  163. package/src/util/retry.ts +1 -44
  164. package/tests/__snapshots__/feed-generation.test.ts.snap +322 -6
  165. package/tests/__snapshots__/indexing.test.ts.snap +0 -6
  166. package/tests/admin/__snapshots__/get-record.test.ts.snap +30 -132
  167. package/tests/admin/__snapshots__/get-repo.test.ts.snap +14 -60
  168. package/tests/admin/__snapshots__/moderation-events.test.ts.snap +146 -0
  169. package/tests/admin/__snapshots__/moderation-statuses.test.ts.snap +64 -0
  170. package/tests/admin/__snapshots__/moderation.test.ts.snap +0 -125
  171. package/tests/admin/get-record.test.ts +5 -9
  172. package/tests/admin/get-repo.test.ts +10 -12
  173. package/tests/admin/moderation-events.test.ts +221 -0
  174. package/tests/admin/moderation-statuses.test.ts +145 -0
  175. package/tests/admin/moderation.test.ts +512 -860
  176. package/tests/admin/repo-search.test.ts +3 -3
  177. package/tests/algos/hot-classic.test.ts +1 -2
  178. package/tests/auth.test.ts +1 -1
  179. package/tests/auto-moderator/fuzzy-matcher.test.ts +2 -1
  180. package/tests/auto-moderator/labeler.test.ts +19 -20
  181. package/tests/auto-moderator/takedowns.test.ts +61 -28
  182. package/tests/blob-resolver.test.ts +4 -2
  183. package/tests/daemon.test.ts +191 -0
  184. package/tests/did-cache.test.ts +20 -5
  185. package/tests/feed-generation.test.ts +57 -9
  186. package/tests/handle-invalidation.test.ts +1 -5
  187. package/tests/indexing.test.ts +20 -13
  188. package/tests/redis-cache.test.ts +231 -0
  189. package/tests/seeds/basic.ts +3 -0
  190. package/tests/subscription/repo.test.ts +4 -7
  191. package/tests/views/__snapshots__/block-lists.test.ts.snap +3 -9
  192. package/tests/views/__snapshots__/blocks.test.ts.snap +0 -9
  193. package/tests/views/__snapshots__/mute-lists.test.ts.snap +5 -5
  194. package/tests/views/__snapshots__/mutes.test.ts.snap +0 -3
  195. package/tests/views/__snapshots__/thread.test.ts.snap +0 -30
  196. package/tests/views/actor-search.test.ts +2 -3
  197. package/tests/views/author-feed.test.ts +42 -36
  198. package/tests/views/follows.test.ts +40 -35
  199. package/tests/views/list-feed.test.ts +17 -9
  200. package/tests/views/notifications.test.ts +13 -9
  201. package/tests/views/profile.test.ts +20 -19
  202. package/tests/views/thread.test.ts +117 -94
  203. package/tests/views/threadgating.test.ts +89 -19
  204. package/tests/views/timeline.test.ts +21 -13
  205. package/dist/api/com/atproto/admin/resolveModerationReports.d.ts +0 -3
  206. package/dist/api/com/atproto/admin/reverseModerationAction.d.ts +0 -3
  207. package/dist/api/com/atproto/admin/takeModerationAction.d.ts +0 -3
  208. package/dist/db/tables/did-cache.d.ts +0 -10
  209. package/dist/feed-gen/best-of-follows.d.ts +0 -29
  210. package/dist/feed-gen/whats-hot.d.ts +0 -29
  211. package/dist/feed-gen/with-friends.d.ts +0 -3
  212. package/dist/label-cache.d.ts +0 -19
  213. package/src/api/com/atproto/admin/getModerationAction.ts +0 -44
  214. package/src/api/com/atproto/admin/getModerationReport.ts +0 -43
  215. package/src/api/com/atproto/admin/resolveModerationReports.ts +0 -24
  216. package/src/api/com/atproto/admin/reverseModerationAction.ts +0 -115
  217. package/src/api/com/atproto/admin/takeModerationAction.ts +0 -156
  218. package/src/db/tables/did-cache.ts +0 -13
  219. package/src/feed-gen/best-of-follows.ts +0 -74
  220. package/src/feed-gen/whats-hot.ts +0 -101
  221. package/src/feed-gen/with-friends.ts +0 -39
  222. package/src/label-cache.ts +0 -90
  223. package/tests/admin/__snapshots__/get-moderation-action.test.ts.snap +0 -172
  224. package/tests/admin/__snapshots__/get-moderation-actions.test.ts.snap +0 -178
  225. package/tests/admin/__snapshots__/get-moderation-report.test.ts.snap +0 -177
  226. package/tests/admin/__snapshots__/get-moderation-reports.test.ts.snap +0 -307
  227. package/tests/admin/get-moderation-action.test.ts +0 -100
  228. package/tests/admin/get-moderation-actions.test.ts +0 -164
  229. package/tests/admin/get-moderation-report.test.ts +0 -100
  230. package/tests/admin/get-moderation-reports.test.ts +0 -332
  231. package/tests/algos/whats-hot.test.ts +0 -118
  232. package/tests/algos/with-friends.test.ts +0 -145
  233. /package/dist/api/com/atproto/admin/{getModerationAction.d.ts → emitModerationEvent.d.ts} +0 -0
  234. /package/dist/api/com/atproto/admin/{getModerationActions.d.ts → getModerationEvent.d.ts} +0 -0
  235. /package/dist/api/com/atproto/admin/{getModerationReport.d.ts → queryModerationEvents.d.ts} +0 -0
  236. /package/dist/api/com/atproto/admin/{getModerationReports.d.ts → queryModerationStatuses.d.ts} +0 -0
@@ -1,76 +1,59 @@
1
1
  import { Generated } from 'kysely'
2
2
  import {
3
- ACKNOWLEDGE,
4
- FLAG,
5
- TAKEDOWN,
6
- ESCALATE,
3
+ REVIEWCLOSED,
4
+ REVIEWOPEN,
5
+ REVIEWESCALATED,
7
6
  } from '../../lexicon/types/com/atproto/admin/defs'
8
- import {
9
- REASONOTHER,
10
- REASONSPAM,
11
- REASONMISLEADING,
12
- REASONRUDE,
13
- REASONSEXUAL,
14
- REASONVIOLATION,
15
- } from '../../lexicon/types/com/atproto/moderation/defs'
16
7
 
17
- export const actionTableName = 'moderation_action'
18
- export const actionSubjectBlobTableName = 'moderation_action_subject_blob'
19
- export const reportTableName = 'moderation_report'
20
- export const reportResolutionTableName = 'moderation_report_resolution'
8
+ export const eventTableName = 'moderation_event'
9
+ export const subjectStatusTableName = 'moderation_subject_status'
21
10
 
22
- export interface ModerationAction {
11
+ export interface ModerationEvent {
23
12
  id: Generated<number>
24
- action: typeof TAKEDOWN | typeof FLAG | typeof ACKNOWLEDGE | typeof ESCALATE
13
+ action:
14
+ | 'com.atproto.admin.defs#modEventTakedown'
15
+ | 'com.atproto.admin.defs#modEventAcknowledge'
16
+ | 'com.atproto.admin.defs#modEventEscalate'
17
+ | 'com.atproto.admin.defs#modEventComment'
18
+ | 'com.atproto.admin.defs#modEventLabel'
19
+ | 'com.atproto.admin.defs#modEventReport'
20
+ | 'com.atproto.admin.defs#modEventMute'
21
+ | 'com.atproto.admin.defs#modEventReverseTakedown'
22
+ | 'com.atproto.admin.defs#modEventEmail'
25
23
  subjectType: 'com.atproto.admin.defs#repoRef' | 'com.atproto.repo.strongRef'
26
24
  subjectDid: string
27
25
  subjectUri: string | null
28
26
  subjectCid: string | null
29
27
  createLabelVals: string | null
30
28
  negateLabelVals: string | null
31
- reason: string
29
+ comment: string | null
32
30
  createdAt: string
33
31
  createdBy: string
34
- reversedAt: string | null
35
- reversedBy: string | null
36
- reversedReason: string | null
37
32
  durationInHours: number | null
38
33
  expiresAt: string | null
34
+ meta: Record<string, string | boolean> | null
35
+ legacyRefId: number | null
39
36
  }
40
37
 
41
- export interface ModerationActionSubjectBlob {
42
- actionId: number
43
- cid: string
44
- }
45
-
46
- export interface ModerationReport {
38
+ export interface ModerationSubjectStatus {
47
39
  id: Generated<number>
48
- subjectType: 'com.atproto.admin.defs#repoRef' | 'com.atproto.repo.strongRef'
49
- subjectDid: string
50
- subjectUri: string | null
51
- subjectCid: string | null
52
- reasonType:
53
- | typeof REASONSPAM
54
- | typeof REASONOTHER
55
- | typeof REASONMISLEADING
56
- | typeof REASONRUDE
57
- | typeof REASONSEXUAL
58
- | typeof REASONVIOLATION
59
- reason: string | null
60
- reportedByDid: string
40
+ did: string
41
+ recordPath: string
42
+ recordCid: string | null
43
+ blobCids: string[] | null
44
+ reviewState: typeof REVIEWCLOSED | typeof REVIEWOPEN | typeof REVIEWESCALATED
61
45
  createdAt: string
62
- }
63
-
64
- export interface ModerationReportResolution {
65
- reportId: number
66
- actionId: number
67
- createdAt: string
68
- createdBy: string
46
+ updatedAt: string
47
+ lastReviewedBy: string | null
48
+ lastReviewedAt: string | null
49
+ lastReportedAt: string | null
50
+ muteUntil: string | null
51
+ suspendUntil: string | null
52
+ takendown: boolean
53
+ comment: string | null
69
54
  }
70
55
 
71
56
  export type PartialDB = {
72
- [actionTableName]: ModerationAction
73
- [actionSubjectBlobTableName]: ModerationActionSubjectBlob
74
- [reportTableName]: ModerationReport
75
- [reportResolutionTableName]: ModerationReportResolution
57
+ [eventTableName]: ModerationEvent
58
+ [subjectStatusTableName]: ModerationSubjectStatus
76
59
  }
package/src/did-cache.ts CHANGED
@@ -1,81 +1,61 @@
1
1
  import PQueue from 'p-queue'
2
2
  import { CacheResult, DidCache, DidDocument } from '@atproto/identity'
3
- import { PrimaryDatabase } from './db'
4
- import { excluded } from './db/util'
5
- import { dbLogger } from './logger'
3
+ import { cacheLogger as log } from './logger'
4
+ import { Redis } from './redis'
6
5
 
7
- export class DidSqlCache implements DidCache {
8
- public pQueue: PQueue | null //null during teardown
6
+ type CacheOptions = {
7
+ staleTTL: number
8
+ maxTTL: number
9
+ }
10
+
11
+ export class DidRedisCache implements DidCache {
12
+ public pQueue: PQueue | null // null during teardown
9
13
 
10
- constructor(
11
- // @TODO perhaps could use both primary and non-primary. not high enough
12
- // throughput to matter right now. also may just move this over to redis before long!
13
- public db: PrimaryDatabase,
14
- public staleTTL: number,
15
- public maxTTL: number,
16
- ) {
14
+ constructor(public redis: Redis, public opts: CacheOptions) {
17
15
  this.pQueue = new PQueue()
18
16
  }
19
17
 
20
- async cacheDid(
21
- did: string,
22
- doc: DidDocument,
23
- prevResult?: CacheResult,
24
- ): Promise<void> {
25
- if (prevResult) {
26
- await this.db.db
27
- .updateTable('did_cache')
28
- .set({ doc, updatedAt: Date.now() })
29
- .where('did', '=', did)
30
- .where('updatedAt', '=', prevResult.updatedAt)
31
- .execute()
32
- } else {
33
- await this.db.db
34
- .insertInto('did_cache')
35
- .values({ did, doc, updatedAt: Date.now() })
36
- .onConflict((oc) =>
37
- oc.column('did').doUpdateSet({
38
- doc: excluded(this.db.db, 'doc'),
39
- updatedAt: excluded(this.db.db, 'updatedAt'),
40
- }),
41
- )
42
- .executeTakeFirst()
43
- }
18
+ async cacheDid(did: string, doc: DidDocument): Promise<void> {
19
+ const item = JSON.stringify({
20
+ doc,
21
+ updatedAt: Date.now(),
22
+ })
23
+ await this.redis.set(did, item, this.opts.maxTTL)
44
24
  }
45
25
 
46
26
  async refreshCache(
47
27
  did: string,
48
28
  getDoc: () => Promise<DidDocument | null>,
49
- prevResult?: CacheResult,
50
29
  ): Promise<void> {
51
30
  this.pQueue?.add(async () => {
52
31
  try {
53
32
  const doc = await getDoc()
54
33
  if (doc) {
55
- await this.cacheDid(did, doc, prevResult)
34
+ await this.cacheDid(did, doc)
56
35
  } else {
57
36
  await this.clearEntry(did)
58
37
  }
59
38
  } catch (err) {
60
- dbLogger.error({ did, err }, 'refreshing did cache failed')
39
+ log.error({ did, err }, 'refreshing did cache failed')
61
40
  }
62
41
  })
63
42
  }
64
43
 
65
44
  async checkCache(did: string): Promise<CacheResult | null> {
66
- const res = await this.db.db
67
- .selectFrom('did_cache')
68
- .where('did', '=', did)
69
- .selectAll()
70
- .executeTakeFirst()
71
- if (!res) return null
72
-
45
+ let got: string | null
46
+ try {
47
+ got = await this.redis.get(did)
48
+ } catch (err) {
49
+ got = null
50
+ log.error({ did, err }, 'error fetching did from cache')
51
+ }
52
+ if (!got) return null
53
+ const { doc, updatedAt } = JSON.parse(got) as CacheResult
73
54
  const now = Date.now()
74
- const updatedAt = new Date(res.updatedAt).getTime()
75
- const expired = now > updatedAt + this.maxTTL
76
- const stale = now > updatedAt + this.staleTTL
55
+ const expired = now > updatedAt + this.opts.maxTTL
56
+ const stale = now > updatedAt + this.opts.staleTTL
77
57
  return {
78
- doc: res.doc,
58
+ doc,
79
59
  updatedAt,
80
60
  did,
81
61
  stale,
@@ -84,14 +64,11 @@ export class DidSqlCache implements DidCache {
84
64
  }
85
65
 
86
66
  async clearEntry(did: string): Promise<void> {
87
- await this.db.db
88
- .deleteFrom('did_cache')
89
- .where('did', '=', did)
90
- .executeTakeFirst()
67
+ await this.redis.del(did)
91
68
  }
92
69
 
93
70
  async clear(): Promise<void> {
94
- await this.db.db.deleteFrom('did_cache').execute()
71
+ throw new Error('Not implemented for redis cache')
95
72
  }
96
73
 
97
74
  async processAll() {
@@ -107,4 +84,4 @@ export class DidSqlCache implements DidCache {
107
84
  }
108
85
  }
109
86
 
110
- export default DidSqlCache
87
+ export default DidRedisCache
@@ -14,7 +14,7 @@ const BSKY_TEAM: NotEmptyArray<string> = [
14
14
  const handler: AlgoHandler = async (
15
15
  ctx: AppContext,
16
16
  params: SkeletonParams,
17
- _viewer: string,
17
+ _viewer: string | null,
18
18
  ): Promise<AlgoResponse> => {
19
19
  const { limit = 50, cursor } = params
20
20
  const db = ctx.db.getReplica('feed')
@@ -11,7 +11,7 @@ const NO_WHATS_HOT_LABELS: NotEmptyArray<string> = ['!no-promote']
11
11
  const handler: AlgoHandler = async (
12
12
  ctx: AppContext,
13
13
  params: SkeletonParams,
14
- _viewer: string,
14
+ _viewer: string | null,
15
15
  ): Promise<AlgoResponse> => {
16
16
  const { limit = 50, cursor } = params
17
17
  const db = ctx.db.getReplica('feed')
@@ -1,9 +1,7 @@
1
1
  import { AtUri } from '@atproto/syntax'
2
2
  import { ids } from '../lexicon/lexicons'
3
- import withFriends from './with-friends'
4
3
  import bskyTeam from './bsky-team'
5
4
  import hotClassic from './hot-classic'
6
- import bestOfFollows from './best-of-follows'
7
5
  import mutuals from './mutuals'
8
6
  import { MountedAlgos } from './types'
9
7
 
@@ -13,9 +11,7 @@ const feedgenUri = (did, name) =>
13
11
  // These are custom algorithms that will be mounted directly onto an AppView
14
12
  // Feel free to remove, update to your own, or serve the following logic at a record that you control
15
13
  export const makeAlgos = (did: string): MountedAlgos => ({
16
- [feedgenUri(did, 'with-friends')]: withFriends,
17
14
  [feedgenUri(did, 'bsky-team')]: bskyTeam,
18
15
  [feedgenUri(did, 'hot-classic')]: hotClassic,
19
- [feedgenUri(did, 'best-of-follows')]: bestOfFollows,
20
16
  [feedgenUri(did, 'mutuals')]: mutuals,
21
17
  })
@@ -3,16 +3,20 @@ import AppContext from '../context'
3
3
  import { paginate } from '../db/pagination'
4
4
  import { AlgoHandler, AlgoResponse } from './types'
5
5
  import { FeedKeyset, getFeedDateThreshold } from '../api/app/bsky/util/feed'
6
+ import { AuthRequiredError } from '@atproto/xrpc-server'
6
7
 
7
8
  const handler: AlgoHandler = async (
8
9
  ctx: AppContext,
9
10
  params: SkeletonParams,
10
- viewer: string,
11
+ viewer: string | null,
11
12
  ): Promise<AlgoResponse> => {
13
+ if (!viewer) {
14
+ throw new AuthRequiredError('This feed requires being logged-in')
15
+ }
16
+
12
17
  const { limit = 50, cursor } = params
13
18
  const db = ctx.db.getReplica('feed')
14
19
  const feedService = ctx.services.feed(db)
15
-
16
20
  const { ref } = db.db.dynamic
17
21
 
18
22
  const mutualsSubquery = db.db
@@ -11,7 +11,7 @@ export type AlgoResponse = {
11
11
  export type AlgoHandler = (
12
12
  ctx: AppContext,
13
13
  params: SkeletonParams,
14
- requester: string,
14
+ viewer: string | null,
15
15
  ) => Promise<AlgoResponse>
16
16
 
17
17
  export type MountedAlgos = Record<string, AlgoHandler>
package/src/index.ts CHANGED
@@ -6,6 +6,12 @@ import { createHttpTerminator, HttpTerminator } from 'http-terminator'
6
6
  import cors from 'cors'
7
7
  import compression from 'compression'
8
8
  import { IdResolver } from '@atproto/identity'
9
+ import {
10
+ RateLimiter,
11
+ RateLimiterOpts,
12
+ Options as XrpcServerOptions,
13
+ } from '@atproto/xrpc-server'
14
+ import { MINUTE } from '@atproto/common'
9
15
  import API, { health, wellKnown, blobResolver } from './api'
10
16
  import { DatabaseCoordinator } from './db'
11
17
  import * as error from './error'
@@ -16,29 +22,31 @@ import { ImageUriBuilder } from './image/uri'
16
22
  import { BlobDiskCache, ImageProcessingServer } from './image/server'
17
23
  import { createServices } from './services'
18
24
  import AppContext from './context'
19
- import DidSqlCache from './did-cache'
25
+ import DidRedisCache from './did-cache'
20
26
  import {
21
27
  ImageInvalidator,
22
28
  ImageProcessingServerInvalidator,
23
29
  } from './image/invalidator'
24
30
  import { BackgroundQueue } from './background'
25
31
  import { MountedAlgos } from './feed-gen/types'
26
- import { LabelCache } from './label-cache'
27
32
  import { NotificationServer } from './notifications'
28
33
  import { AtpAgent } from '@atproto/api'
29
34
  import { Keypair } from '@atproto/crypto'
35
+ import { Redis } from './redis'
30
36
 
31
37
  export type { ServerConfigValues } from './config'
32
38
  export type { MountedAlgos } from './feed-gen/types'
33
39
  export { ServerConfig } from './config'
34
40
  export { Database, PrimaryDatabase, DatabaseCoordinator } from './db'
35
- export { PeriodicModerationActionReversal } from './db/periodic-moderation-action-reversal'
41
+ export { PeriodicModerationEventReversal } from './db/periodic-moderation-event-reversal'
36
42
  export { Redis } from './redis'
37
43
  export { ViewMaintainer } from './db/views'
38
44
  export { AppContext } from './context'
39
45
  export { makeAlgos } from './feed-gen'
46
+ export * from './daemon'
40
47
  export * from './indexer'
41
48
  export * from './ingester'
49
+ export { MigrateModerationData } from './migrate-moderation-data'
42
50
 
43
51
  export class BskyAppView {
44
52
  public ctx: AppContext
@@ -54,23 +62,25 @@ export class BskyAppView {
54
62
 
55
63
  static create(opts: {
56
64
  db: DatabaseCoordinator
65
+ redis: Redis
57
66
  config: ServerConfig
58
67
  signingKey: Keypair
59
68
  imgInvalidator?: ImageInvalidator
60
69
  algos?: MountedAlgos
61
70
  }): BskyAppView {
62
- const { db, config, signingKey, algos = {} } = opts
71
+ const { db, redis, config, signingKey, algos = {} } = opts
63
72
  let maybeImgInvalidator = opts.imgInvalidator
64
73
  const app = express()
74
+ app.set('trust proxy', true)
65
75
  app.use(cors())
66
76
  app.use(loggerMiddleware)
67
77
  app.use(compression())
68
78
 
69
- const didCache = new DidSqlCache(
70
- db.getPrimary(),
71
- config.didCacheStaleTTL,
72
- config.didCacheMaxTTL,
73
- )
79
+ const didCache = new DidRedisCache(redis.withNamespace('did-doc'), {
80
+ staleTTL: config.didCacheStaleTTL,
81
+ maxTTL: config.didCacheMaxTTL,
82
+ })
83
+
74
84
  const idResolver = new IdResolver({
75
85
  plcUrl: config.didPlcUrl,
76
86
  didCache,
@@ -101,7 +111,7 @@ export class BskyAppView {
101
111
  }
102
112
 
103
113
  const backgroundQueue = new BackgroundQueue(db.getPrimary())
104
- const labelCache = new LabelCache(db.getPrimary())
114
+
105
115
  const notifServer = new NotificationServer(db.getPrimary())
106
116
  const searchAgent = config.searchEndpoint
107
117
  ? new AtpAgent({ service: config.searchEndpoint })
@@ -110,7 +120,11 @@ export class BskyAppView {
110
120
  const services = createServices({
111
121
  imgUriBuilder,
112
122
  imgInvalidator,
113
- labelCache,
123
+ labelCacheOpts: {
124
+ redis: redis.withNamespace('label'),
125
+ staleTTL: config.labelCacheStaleTTL,
126
+ maxTTL: config.labelCacheMaxTTL,
127
+ },
114
128
  })
115
129
 
116
130
  const ctx = new AppContext({
@@ -121,21 +135,48 @@ export class BskyAppView {
121
135
  signingKey,
122
136
  idResolver,
123
137
  didCache,
124
- labelCache,
138
+ redis,
125
139
  backgroundQueue,
126
140
  searchAgent,
127
141
  algos,
128
142
  notifServer,
129
143
  })
130
144
 
131
- let server = createServer({
145
+ const xrpcOpts: XrpcServerOptions = {
132
146
  validateResponse: config.debugMode,
133
147
  payload: {
134
148
  jsonLimit: 100 * 1024, // 100kb
135
149
  textLimit: 100 * 1024, // 100kb
136
150
  blobLimit: 5 * 1024 * 1024, // 5mb
137
151
  },
138
- })
152
+ }
153
+ if (config.rateLimitsEnabled) {
154
+ const rlCreator = (opts: RateLimiterOpts) =>
155
+ RateLimiter.redis(redis.driver, {
156
+ bypassSecret: config.rateLimitBypassKey,
157
+ bypassIps: config.rateLimitBypassIps,
158
+ ...opts,
159
+ })
160
+ xrpcOpts['rateLimits'] = {
161
+ creator: rlCreator,
162
+ global: [
163
+ {
164
+ name: 'global-unauthed-ip',
165
+ durationMs: 5 * MINUTE,
166
+ points: 3000,
167
+ calcKey: (ctx) => (ctx.auth ? null : ctx.req.ip),
168
+ },
169
+ {
170
+ name: 'global-authed-did',
171
+ durationMs: 5 * MINUTE,
172
+ points: 3000,
173
+ calcKey: (ctx) => ctx.auth?.credentials?.did ?? null,
174
+ },
175
+ ],
176
+ }
177
+ }
178
+
179
+ let server = createServer(xrpcOpts)
139
180
 
140
181
  server = API(server, ctx)
141
182
 
@@ -184,7 +225,6 @@ export class BskyAppView {
184
225
  'background queue stats',
185
226
  )
186
227
  }, 10000)
187
- this.ctx.labelCache.start()
188
228
  const server = this.app.listen(this.ctx.cfg.port)
189
229
  this.server = server
190
230
  server.keepAliveTimeout = 90000
@@ -195,11 +235,11 @@ export class BskyAppView {
195
235
  return server
196
236
  }
197
237
 
198
- async destroy(opts?: { skipDb: boolean }): Promise<void> {
199
- this.ctx.labelCache.stop()
238
+ async destroy(opts?: { skipDb: boolean; skipRedis: boolean }): Promise<void> {
200
239
  await this.ctx.didCache.destroy()
201
240
  await this.terminator?.terminate()
202
241
  await this.ctx.backgroundQueue.destroy()
242
+ if (!opts?.skipRedis) await this.ctx.redis.destroy()
203
243
  if (!opts?.skipDb) await this.ctx.db.close()
204
244
  clearInterval(this.dbStatsInterval)
205
245
  }
@@ -12,6 +12,7 @@ export class IndexerContext {
12
12
  private opts: {
13
13
  db: PrimaryDatabase
14
14
  redis: Redis
15
+ redisCache: Redis
15
16
  cfg: IndexerConfig
16
17
  services: Services
17
18
  idResolver: IdResolver
@@ -29,6 +30,10 @@ export class IndexerContext {
29
30
  return this.opts.redis
30
31
  }
31
32
 
33
+ get redisCache(): Redis {
34
+ return this.opts.redisCache
35
+ }
36
+
32
37
  get cfg(): IndexerConfig {
33
38
  return this.opts.cfg
34
39
  }
@@ -2,7 +2,7 @@ import express from 'express'
2
2
  import { IdResolver } from '@atproto/identity'
3
3
  import { BackgroundQueue } from '../background'
4
4
  import { PrimaryDatabase } from '../db'
5
- import DidSqlCache from '../did-cache'
5
+ import DidRedisCache from '../did-cache'
6
6
  import log from './logger'
7
7
  import { dbLogger } from '../logger'
8
8
  import { IndexerConfig } from './config'
@@ -40,15 +40,15 @@ export class BskyIndexer {
40
40
  static create(opts: {
41
41
  db: PrimaryDatabase
42
42
  redis: Redis
43
+ redisCache: Redis
43
44
  cfg: IndexerConfig
44
45
  imgInvalidator?: ImageInvalidator
45
46
  }): BskyIndexer {
46
- const { db, redis, cfg } = opts
47
- const didCache = new DidSqlCache(
48
- db,
49
- cfg.didCacheStaleTTL,
50
- cfg.didCacheMaxTTL,
51
- )
47
+ const { db, redis, redisCache, cfg } = opts
48
+ const didCache = new DidRedisCache(redisCache.withNamespace('did-doc'), {
49
+ staleTTL: cfg.didCacheStaleTTL,
50
+ maxTTL: cfg.didCacheMaxTTL,
51
+ })
52
52
  const idResolver = new IdResolver({
53
53
  plcUrl: cfg.didPlcUrl,
54
54
  didCache,
@@ -81,6 +81,7 @@ export class BskyIndexer {
81
81
  const ctx = new IndexerContext({
82
82
  db,
83
83
  redis,
84
+ redisCache,
84
85
  cfg,
85
86
  services,
86
87
  idResolver,
@@ -139,7 +140,9 @@ export class BskyIndexer {
139
140
  if (this.closeServer) await this.closeServer()
140
141
  await this.sub.destroy()
141
142
  clearInterval(this.subStatsInterval)
143
+ await this.ctx.didCache.destroy()
142
144
  if (!opts?.skipRedis) await this.ctx.redis.destroy()
145
+ if (!opts?.skipRedis) await this.ctx.redisCache.destroy()
143
146
  if (!opts?.skipDb) await this.ctx.db.close()
144
147
  clearInterval(this.dbStatsInterval)
145
148
  }