@atproto/ozone 0.0.11 → 0.0.13

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 (54) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/api/label/queryLabels.d.ts +3 -0
  3. package/dist/api/label/subscribeLabels.d.ts +3 -0
  4. package/dist/config/config.d.ts +3 -0
  5. package/dist/config/env.d.ts +3 -0
  6. package/dist/context.d.ts +3 -0
  7. package/dist/db/index.js +3 -1
  8. package/dist/db/index.js.map +2 -2
  9. package/dist/db/schema/label.d.ts +4 -0
  10. package/dist/index.js +875 -454
  11. package/dist/index.js.map +3 -3
  12. package/dist/lexicon/index.d.ts +2 -0
  13. package/dist/lexicon/lexicons.d.ts +27 -0
  14. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +1 -1
  15. package/dist/lexicon/types/com/atproto/admin/updateAccountPassword.d.ts +26 -0
  16. package/dist/logger.d.ts +1 -0
  17. package/dist/mod-service/util.d.ts +3 -0
  18. package/dist/sequencer/index.d.ts +2 -0
  19. package/dist/sequencer/outbox.d.ts +16 -0
  20. package/dist/sequencer/sequencer.d.ts +33 -0
  21. package/package.json +10 -10
  22. package/src/api/admin/emitModerationEvent.ts +16 -10
  23. package/src/api/index.ts +4 -0
  24. package/src/api/label/queryLabels.ts +58 -0
  25. package/src/api/label/subscribeLabels.ts +25 -0
  26. package/src/api/temp/fetchLabels.ts +2 -4
  27. package/src/config/config.ts +6 -0
  28. package/src/config/env.ts +6 -0
  29. package/src/context.ts +12 -0
  30. package/src/db/migrations/20231219T205730722Z-init.ts +7 -1
  31. package/src/db/schema/label.ts +7 -0
  32. package/src/index.ts +2 -0
  33. package/src/lexicon/index.ts +12 -0
  34. package/src/lexicon/lexicons.ts +32 -1
  35. package/src/lexicon/types/app/bsky/actor/defs.ts +2 -0
  36. package/src/lexicon/types/com/atproto/admin/updateAccountPassword.ts +39 -0
  37. package/src/logger.ts +2 -0
  38. package/src/mod-service/index.ts +73 -72
  39. package/src/mod-service/status.ts +3 -0
  40. package/src/mod-service/util.ts +17 -0
  41. package/src/mod-service/views.ts +2 -5
  42. package/src/sequencer/index.ts +2 -0
  43. package/src/sequencer/outbox.ts +122 -0
  44. package/src/sequencer/sequencer.ts +143 -0
  45. package/tests/__snapshots__/moderation-events.test.ts.snap +53 -75
  46. package/tests/__snapshots__/moderation.test.ts.snap +4 -4
  47. package/tests/moderation-appeals.test.ts +19 -7
  48. package/tests/moderation-events.test.ts +7 -7
  49. package/tests/moderation-statuses.test.ts +2 -2
  50. package/tests/moderation.test.ts +14 -13
  51. package/tests/query-labels.test.ts +163 -0
  52. package/tests/repo-search.test.ts +0 -1
  53. package/tests/sequencer.test.ts +222 -0
  54. package/tests/server.test.ts +2 -0
@@ -20,6 +20,7 @@ import * as ComAtprotoAdminSearchRepos from './types/com/atproto/admin/searchRep
20
20
  import * as ComAtprotoAdminSendEmail from './types/com/atproto/admin/sendEmail';
21
21
  import * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/updateAccountEmail';
22
22
  import * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle';
23
+ import * as ComAtprotoAdminUpdateAccountPassword from './types/com/atproto/admin/updateAccountPassword';
23
24
  import * as ComAtprotoAdminUpdateCommunicationTemplate from './types/com/atproto/admin/updateCommunicationTemplate';
24
25
  import * as ComAtprotoAdminUpdateSubjectStatus from './types/com/atproto/admin/updateSubjectStatus';
25
26
  import * as ComAtprotoIdentityGetRecommendedDidCredentials from './types/com/atproto/identity/getRecommendedDidCredentials';
@@ -192,6 +193,7 @@ export declare class ComAtprotoAdminNS {
192
193
  sendEmail<AV extends AuthVerifier>(cfg: ConfigOf<AV, ComAtprotoAdminSendEmail.Handler<ExtractAuth<AV>>, ComAtprotoAdminSendEmail.HandlerReqCtx<ExtractAuth<AV>>>): void;
193
194
  updateAccountEmail<AV extends AuthVerifier>(cfg: ConfigOf<AV, ComAtprotoAdminUpdateAccountEmail.Handler<ExtractAuth<AV>>, ComAtprotoAdminUpdateAccountEmail.HandlerReqCtx<ExtractAuth<AV>>>): void;
194
195
  updateAccountHandle<AV extends AuthVerifier>(cfg: ConfigOf<AV, ComAtprotoAdminUpdateAccountHandle.Handler<ExtractAuth<AV>>, ComAtprotoAdminUpdateAccountHandle.HandlerReqCtx<ExtractAuth<AV>>>): void;
196
+ updateAccountPassword<AV extends AuthVerifier>(cfg: ConfigOf<AV, ComAtprotoAdminUpdateAccountPassword.Handler<ExtractAuth<AV>>, ComAtprotoAdminUpdateAccountPassword.HandlerReqCtx<ExtractAuth<AV>>>): void;
195
197
  updateCommunicationTemplate<AV extends AuthVerifier>(cfg: ConfigOf<AV, ComAtprotoAdminUpdateCommunicationTemplate.Handler<ExtractAuth<AV>>, ComAtprotoAdminUpdateCommunicationTemplate.HandlerReqCtx<ExtractAuth<AV>>>): void;
196
198
  updateSubjectStatus<AV extends AuthVerifier>(cfg: ConfigOf<AV, ComAtprotoAdminUpdateSubjectStatus.Handler<ExtractAuth<AV>>, ComAtprotoAdminUpdateSubjectStatus.HandlerReqCtx<ExtractAuth<AV>>>): void;
197
199
  }
@@ -1695,6 +1695,32 @@ export declare const schemaDict: {
1695
1695
  };
1696
1696
  };
1697
1697
  };
1698
+ ComAtprotoAdminUpdateAccountPassword: {
1699
+ lexicon: number;
1700
+ id: string;
1701
+ defs: {
1702
+ main: {
1703
+ type: string;
1704
+ description: string;
1705
+ input: {
1706
+ encoding: string;
1707
+ schema: {
1708
+ type: string;
1709
+ required: string[];
1710
+ properties: {
1711
+ did: {
1712
+ type: string;
1713
+ format: string;
1714
+ };
1715
+ password: {
1716
+ type: string;
1717
+ };
1718
+ };
1719
+ };
1720
+ };
1721
+ };
1722
+ };
1723
+ };
1698
1724
  ComAtprotoAdminUpdateCommunicationTemplate: {
1699
1725
  lexicon: number;
1700
1726
  id: string;
@@ -8205,6 +8231,7 @@ export declare const ids: {
8205
8231
  ComAtprotoAdminSendEmail: string;
8206
8232
  ComAtprotoAdminUpdateAccountEmail: string;
8207
8233
  ComAtprotoAdminUpdateAccountHandle: string;
8234
+ ComAtprotoAdminUpdateAccountPassword: string;
8208
8235
  ComAtprotoAdminUpdateCommunicationTemplate: string;
8209
8236
  ComAtprotoAdminUpdateSubjectStatus: string;
8210
8237
  ComAtprotoIdentityGetRecommendedDidCredentials: string;
@@ -54,7 +54,7 @@ export interface ViewerState {
54
54
  }
55
55
  export declare function isViewerState(v: unknown): v is ViewerState;
56
56
  export declare function validateViewerState(v: unknown): ValidationResult;
57
- export type Preferences = (AdultContentPref | ContentLabelPref | SavedFeedsPref | PersonalDetailsPref | FeedViewPref | ThreadViewPref | InterestsPref | {
57
+ export type Preferences = (AdultContentPref | ContentLabelPref | SavedFeedsPref | PersonalDetailsPref | FeedViewPref | ThreadViewPref | InterestsPref | MutedWordsPref | HiddenPostsPref | {
58
58
  $type: string;
59
59
  [k: string]: unknown;
60
60
  })[];
@@ -0,0 +1,26 @@
1
+ import express from 'express';
2
+ import { HandlerAuth } from '@atproto/xrpc-server';
3
+ export interface QueryParams {
4
+ }
5
+ export interface InputSchema {
6
+ did: string;
7
+ password: string;
8
+ [k: string]: unknown;
9
+ }
10
+ export interface HandlerInput {
11
+ encoding: 'application/json';
12
+ body: InputSchema;
13
+ }
14
+ export interface HandlerError {
15
+ status: number;
16
+ message?: string;
17
+ }
18
+ export type HandlerOutput = HandlerError | void;
19
+ export type HandlerReqCtx<HA extends HandlerAuth = never> = {
20
+ auth: HA;
21
+ params: QueryParams;
22
+ input: HandlerInput;
23
+ req: express.Request;
24
+ res: express.Response;
25
+ };
26
+ export type Handler<HA extends HandlerAuth = never> = (ctx: HandlerReqCtx<HA>) => Promise<HandlerOutput> | HandlerOutput;
package/dist/logger.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { subsystemLogger } from '@atproto/common';
2
2
  export declare const dbLogger: ReturnType<typeof subsystemLogger>;
3
+ export declare const seqLogger: ReturnType<typeof subsystemLogger>;
3
4
  export declare const httpLogger: ReturnType<typeof subsystemLogger>;
4
5
  export declare const langLogger: ReturnType<typeof subsystemLogger>;
5
6
  export declare const loggerMiddleware: import("pino-http").HttpLogger;
@@ -0,0 +1,3 @@
1
+ import { LabelRow } from '../db/schema/label';
2
+ import { Label } from '../lexicon/types/com/atproto/label/defs';
3
+ export declare const formatLabel: (row: LabelRow) => Label;
@@ -0,0 +1,2 @@
1
+ export * from './sequencer';
2
+ export * from './outbox';
@@ -0,0 +1,16 @@
1
+ import { AsyncBuffer } from '@atproto/common';
2
+ import { Sequencer, LabelsEvt } from './sequencer';
3
+ export type OutboxOpts = {
4
+ maxBufferSize: number;
5
+ };
6
+ export declare class Outbox {
7
+ sequencer: Sequencer;
8
+ private caughtUp;
9
+ lastSeen: number;
10
+ cutoverBuffer: LabelsEvt[];
11
+ outBuffer: AsyncBuffer<LabelsEvt>;
12
+ constructor(sequencer: Sequencer, opts?: Partial<OutboxOpts>);
13
+ events(backfillCursor?: number, signal?: AbortSignal): AsyncGenerator<LabelsEvt>;
14
+ getBackfill(backfillCursor: number): AsyncGenerator<LabelsEvt, void, unknown>;
15
+ }
16
+ export default Outbox;
@@ -0,0 +1,33 @@
1
+ import TypedEmitter from 'typed-emitter';
2
+ import Database from '../db';
3
+ import { Labels as LabelsEvt } from '../lexicon/types/com/atproto/label/subscribeLabels';
4
+ import { Label as LabelTable } from '../db/schema/label';
5
+ import { Selectable } from 'kysely';
6
+ import { PoolClient } from 'pg';
7
+ export type { Labels as LabelsEvt } from '../lexicon/types/com/atproto/label/subscribeLabels';
8
+ type LabelRow = Selectable<LabelTable>;
9
+ declare const Sequencer_base: new () => SequencerEmitter;
10
+ export declare class Sequencer extends Sequencer_base {
11
+ db: Database;
12
+ lastSeen: number;
13
+ destroyed: boolean;
14
+ pollPromise: Promise<void> | undefined;
15
+ queued: boolean;
16
+ conn: PoolClient | undefined;
17
+ constructor(db: Database, lastSeen?: number);
18
+ start(): Promise<void>;
19
+ destroy(): Promise<void>;
20
+ curr(): Promise<number | null>;
21
+ next(cursor: number): Promise<LabelRow | null>;
22
+ requestLabelRange(opts: {
23
+ earliestId?: number;
24
+ limit?: number;
25
+ }): Promise<LabelsEvt[]>;
26
+ private poll;
27
+ }
28
+ type SequencerEvents = {
29
+ events: (evts: LabelsEvt[]) => void;
30
+ close: () => void;
31
+ };
32
+ export type SequencerEmitter = TypedEmitter<SequencerEvents>;
33
+ export default Sequencer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/ozone",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "license": "MIT",
5
5
  "description": "Backend service for moderating the Bluesky network.",
6
6
  "keywords": [
@@ -30,13 +30,13 @@
30
30
  "pino-http": "^8.2.1",
31
31
  "typed-emitter": "^2.1.0",
32
32
  "uint8arrays": "3.0.0",
33
- "@atproto/api": "^0.10.0",
33
+ "@atproto/api": "^0.10.2",
34
34
  "@atproto/common": "^0.3.3",
35
35
  "@atproto/crypto": "^0.3.0",
36
36
  "@atproto/identity": "^0.3.2",
37
- "@atproto/lexicon": "^0.3.1",
38
- "@atproto/syntax": "^0.1.5",
39
- "@atproto/xrpc-server": "^0.4.2"
37
+ "@atproto/lexicon": "^0.3.2",
38
+ "@atproto/syntax": "^0.2.0",
39
+ "@atproto/xrpc-server": "^0.4.3"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@did-plc/server": "^0.0.1",
@@ -46,11 +46,11 @@
46
46
  "@types/pg": "^8.6.6",
47
47
  "@types/qs": "^6.9.7",
48
48
  "axios": "^0.27.2",
49
- "@atproto/api": "^0.10.0",
50
- "@atproto/dev-env": "^0.2.32",
51
- "@atproto/lex-cli": "^0.3.0",
52
- "@atproto/pds": "^0.4.0",
53
- "@atproto/xrpc": "^0.4.1"
49
+ "@atproto/api": "^0.10.2",
50
+ "@atproto/dev-env": "^0.2.34",
51
+ "@atproto/lex-cli": "^0.3.1",
52
+ "@atproto/pds": "^0.4.2",
53
+ "@atproto/xrpc": "^0.4.2"
54
54
  },
55
55
  "scripts": {
56
56
  "codegen": "lex gen-server ./src/lexicon ../../lexicons/com/atproto/*/* ../../lexicons/app/bsky/*/*",
@@ -27,17 +27,23 @@ export default function (server: Server, ctx: AppContext) {
27
27
 
28
28
  // apply access rules
29
29
 
30
- // if less than moderator access then can not takedown an account
31
- if (!access.moderator && isTakedownEvent && subject.isRepo()) {
32
- throw new AuthRequiredError(
33
- 'Must be a full moderator to perform an account takedown',
34
- )
35
- }
36
30
  // if less than moderator access then can only take ack and escalation actions
37
- if (!access.moderator && (isTakedownEvent || isReverseTakedownEvent)) {
38
- throw new AuthRequiredError(
39
- 'Must be a full moderator to take this type of action',
40
- )
31
+ if (isTakedownEvent || isReverseTakedownEvent) {
32
+ if (!access.moderator) {
33
+ throw new AuthRequiredError(
34
+ 'Must be a full moderator to take this type of action',
35
+ )
36
+ }
37
+
38
+ // Non admins should not be able to take down feed generators
39
+ if (
40
+ !access.admin &&
41
+ subject.recordPath?.includes('app.bsky.feed.generator/')
42
+ ) {
43
+ throw new AuthRequiredError(
44
+ 'Must be a full admin to take this type of action on feed generators',
45
+ )
46
+ }
41
47
  }
42
48
  // if less than moderator access then can not apply labels
43
49
  if (!access.moderator && isLabelEvent) {
package/src/api/index.ts CHANGED
@@ -8,6 +8,8 @@ import getRepo from './admin/getRepo'
8
8
  import queryModerationStatuses from './admin/queryModerationStatuses'
9
9
  import queryModerationEvents from './admin/queryModerationEvents'
10
10
  import getModerationEvent from './admin/getModerationEvent'
11
+ import queryLabels from './label/queryLabels'
12
+ import subscribeLabels from './label/subscribeLabels'
11
13
  import fetchLabels from './temp/fetchLabels'
12
14
  import createCommunicationTemplate from './admin/createCommunicationTemplate'
13
15
  import updateCommunicationTemplate from './admin/updateCommunicationTemplate'
@@ -27,6 +29,8 @@ export default function (server: Server, ctx: AppContext) {
27
29
  getModerationEvent(server, ctx)
28
30
  queryModerationEvents(server, ctx)
29
31
  queryModerationStatuses(server, ctx)
32
+ queryLabels(server, ctx)
33
+ subscribeLabels(server, ctx)
30
34
  fetchLabels(server, ctx)
31
35
  listCommunicationTemplates(server, ctx)
32
36
  createCommunicationTemplate(server, ctx)
@@ -0,0 +1,58 @@
1
+ import { Server } from '../../lexicon'
2
+ import AppContext from '../../context'
3
+ import { InvalidRequestError } from '@atproto/xrpc-server'
4
+ import { sql } from 'kysely'
5
+ import { formatLabel } from '../../mod-service/util'
6
+
7
+ export default function (server: Server, ctx: AppContext) {
8
+ server.com.atproto.label.queryLabels(async ({ params }) => {
9
+ const { uriPatterns, sources, limit, cursor } = params
10
+ let builder = ctx.db.db.selectFrom('label').selectAll().limit(limit)
11
+ // if includes '*', then we don't need a where clause
12
+ if (!uriPatterns.includes('*')) {
13
+ builder = builder.where((qb) => {
14
+ // starter where clause that is always false so that we can chain `orWhere`s
15
+ qb = qb.where(sql`1 = 0`)
16
+ for (const pattern of uriPatterns) {
17
+ // if no '*', then we're looking for an exact match
18
+ if (!pattern.includes('*')) {
19
+ qb = qb.orWhere('uri', '=', pattern)
20
+ } else {
21
+ if (pattern.indexOf('*') < pattern.length - 1) {
22
+ throw new InvalidRequestError(`invalid pattern: ${pattern}`)
23
+ }
24
+ const searchPattern = pattern
25
+ .slice(0, -1)
26
+ .replaceAll('%', '') // sanitize search pattern
27
+ .replaceAll('_', '\\_') // escape any underscores
28
+ qb = qb.orWhere('uri', 'like', `${searchPattern}%`)
29
+ }
30
+ }
31
+ return qb
32
+ })
33
+ }
34
+ if (sources && sources.length > 0) {
35
+ builder = builder.where('src', 'in', sources)
36
+ }
37
+ if (cursor) {
38
+ const cursorId = parseInt(cursor, 10)
39
+ if (isNaN(cursorId)) {
40
+ throw new InvalidRequestError('invalid cursor')
41
+ }
42
+ builder = builder.where('id', '>', cursorId)
43
+ }
44
+
45
+ const res = await builder.execute()
46
+
47
+ const labels = res.map((l) => formatLabel(l))
48
+ const resCursor = res.at(-1)?.id.toString(10)
49
+
50
+ return {
51
+ encoding: 'application/json',
52
+ body: {
53
+ cursor: resCursor,
54
+ labels,
55
+ },
56
+ }
57
+ })
58
+ }
@@ -0,0 +1,25 @@
1
+ import { Server } from '../../lexicon'
2
+ import AppContext from '../../context'
3
+ import Outbox from '../../sequencer/outbox'
4
+ import { InvalidRequestError } from '@atproto/xrpc-server'
5
+
6
+ export default function (server: Server, ctx: AppContext) {
7
+ server.com.atproto.label.subscribeLabels(async function* ({
8
+ params,
9
+ signal,
10
+ }) {
11
+ const { cursor } = params
12
+ const outbox = new Outbox(ctx.sequencer)
13
+
14
+ if (cursor !== undefined) {
15
+ const curr = await ctx.sequencer.curr()
16
+ if (cursor > (curr ?? 0)) {
17
+ throw new InvalidRequestError('Cursor in the future.', 'FutureCursor')
18
+ }
19
+ }
20
+
21
+ for await (const evt of outbox.events(cursor, signal)) {
22
+ yield { $type: 'com.atproto.label.subscribeLabels#labels', ...evt }
23
+ }
24
+ })
25
+ }
@@ -1,5 +1,6 @@
1
1
  import { Server } from '../../lexicon'
2
2
  import AppContext from '../../context'
3
+ import { formatLabel } from '../../mod-service/util'
3
4
  import {
4
5
  UNSPECCED_TAKEDOWN_BLOBS_LABEL,
5
6
  UNSPECCED_TAKEDOWN_LABEL,
@@ -28,10 +29,7 @@ export default function (server: Server, ctx: AppContext) {
28
29
  .limit(limit)
29
30
  .execute()
30
31
 
31
- const labels = labelRes.map((l) => ({
32
- ...l,
33
- cid: l.cid === '' ? undefined : l.cid,
34
- }))
32
+ const labels = labelRes.map((l) => formatLabel(l))
35
33
 
36
34
  return {
37
35
  encoding: 'application/json',
@@ -19,6 +19,9 @@ export const envToCfg = (env: OzoneEnvironment): OzoneConfig => {
19
19
  const dbCfg: OzoneConfig['db'] = {
20
20
  postgresUrl: env.dbPostgresUrl,
21
21
  postgresSchema: env.dbPostgresSchema,
22
+ poolSize: env.dbPoolSize,
23
+ poolMaxUses: env.dbPoolMaxUses,
24
+ poolIdleTimeoutMs: env.dbPoolIdleTimeoutMs,
22
25
  }
23
26
 
24
27
  assert(env.appviewUrl)
@@ -67,6 +70,9 @@ export type ServiceConfig = {
67
70
  export type DatabaseConfig = {
68
71
  postgresUrl: string
69
72
  postgresSchema?: string
73
+ poolSize?: number
74
+ poolMaxUses?: number
75
+ poolIdleTimeoutMs?: number
70
76
  }
71
77
 
72
78
  export type AppviewConfig = {
package/src/config/env.ts CHANGED
@@ -13,6 +13,9 @@ export const readEnv = (): OzoneEnvironment => {
13
13
  pdsDid: envStr('OZONE_PDS_DID'),
14
14
  dbPostgresUrl: envStr('OZONE_DB_POSTGRES_URL'),
15
15
  dbPostgresSchema: envStr('OZONE_DB_POSTGRES_SCHEMA'),
16
+ dbPoolSize: envInt('OZONE_DB_POOL_SIZE'),
17
+ dbPoolMaxUses: envInt('OZONE_DB_POOL_MAX_USES'),
18
+ dbPoolIdleTimeoutMs: envInt('OZONE_DB_POOL_IDLE_TIMEOUT_MS'),
16
19
  didPlcUrl: envStr('OZONE_DID_PLC_URL'),
17
20
  adminPassword: envStr('OZONE_ADMIN_PASSWORD'),
18
21
  moderatorPassword: envStr('OZONE_MODERATOR_PASSWORD'),
@@ -33,6 +36,9 @@ export type OzoneEnvironment = {
33
36
  pdsDid?: string
34
37
  dbPostgresUrl?: string
35
38
  dbPostgresSchema?: string
39
+ dbPoolSize?: number
40
+ dbPoolMaxUses?: number
41
+ dbPoolIdleTimeoutMs?: number
36
42
  didPlcUrl?: string
37
43
  adminPassword?: string
38
44
  moderatorPassword?: string
package/src/context.ts CHANGED
@@ -10,6 +10,7 @@ import * as auth from './auth'
10
10
  import { BackgroundQueue } from './background'
11
11
  import assert from 'assert'
12
12
  import { EventPusher } from './daemon'
13
+ import Sequencer from './sequencer/sequencer'
13
14
  import {
14
15
  CommunicationTemplateService,
15
16
  CommunicationTemplateServiceCreator,
@@ -25,6 +26,7 @@ export type AppContextOptions = {
25
26
  signingKey: Keypair
26
27
  idResolver: IdResolver
27
28
  backgroundQueue: BackgroundQueue
29
+ sequencer: Sequencer
28
30
  }
29
31
 
30
32
  export class AppContext {
@@ -38,6 +40,9 @@ export class AppContext {
38
40
  const db = new Database({
39
41
  url: cfg.db.postgresUrl,
40
42
  schema: cfg.db.postgresSchema,
43
+ poolSize: cfg.db.poolSize,
44
+ poolMaxUses: cfg.db.poolMaxUses,
45
+ poolIdleTimeoutMs: cfg.db.poolIdleTimeoutMs,
41
46
  })
42
47
  const signingKey = await Secp256k1Keypair.import(secrets.signingKeyHex)
43
48
  const appviewAgent = new AtpAgent({ service: cfg.appview.url })
@@ -74,6 +79,8 @@ export class AppContext {
74
79
  plcUrl: cfg.identity.plcUrl,
75
80
  })
76
81
 
82
+ const sequencer = new Sequencer(db)
83
+
77
84
  return new AppContext(
78
85
  {
79
86
  db,
@@ -85,6 +92,7 @@ export class AppContext {
85
92
  signingKey,
86
93
  idResolver,
87
94
  backgroundQueue,
95
+ sequencer,
88
96
  ...(overrides ?? {}),
89
97
  },
90
98
  secrets,
@@ -139,6 +147,10 @@ export class AppContext {
139
147
  return this.opts.backgroundQueue
140
148
  }
141
149
 
150
+ get sequencer(): Sequencer {
151
+ return this.opts.sequencer
152
+ }
153
+
142
154
  get authVerifier() {
143
155
  return auth.authVerifier(this.idResolver, { aud: this.cfg.service.did })
144
156
  }
@@ -68,13 +68,19 @@ export async function up(db: Kysely<unknown>): Promise<void> {
68
68
  // Label
69
69
  await db.schema
70
70
  .createTable('label')
71
+ .addColumn('id', 'bigserial', (col) => col.primaryKey())
71
72
  .addColumn('src', 'varchar', (col) => col.notNull())
72
73
  .addColumn('uri', 'varchar', (col) => col.notNull())
73
74
  .addColumn('cid', 'varchar', (col) => col.notNull())
74
75
  .addColumn('val', 'varchar', (col) => col.notNull())
75
76
  .addColumn('neg', 'boolean', (col) => col.notNull())
76
77
  .addColumn('cts', 'varchar', (col) => col.notNull())
77
- .addPrimaryKeyConstraint('label_pkey', ['src', 'uri', 'cid', 'val'])
78
+ .execute()
79
+ await db.schema
80
+ .createIndex('unique_label_idx')
81
+ .unique()
82
+ .on('label')
83
+ .columns(['src', 'uri', 'cid', 'val'])
78
84
  .execute()
79
85
  await db.schema
80
86
  .createIndex('label_uri_index')
@@ -1,6 +1,9 @@
1
+ import { Generated, Selectable } from 'kysely'
2
+
1
3
  export const tableName = 'label'
2
4
 
3
5
  export interface Label {
6
+ id: Generated<number>
4
7
  src: string
5
8
  uri: string
6
9
  cid: string
@@ -9,4 +12,8 @@ export interface Label {
9
12
  cts: string
10
13
  }
11
14
 
15
+ export type LabelRow = Selectable<Label>
16
+
12
17
  export type PartialDB = { [tableName]: Label }
18
+
19
+ export const LabelChannel = 'label_channel' // used with notify/listen
package/src/index.ts CHANGED
@@ -81,6 +81,7 @@ export class OzoneService {
81
81
  'background queue stats',
82
82
  )
83
83
  }, 10000)
84
+ await this.ctx.sequencer.start()
84
85
  const server = this.app.listen(this.ctx.cfg.service.port)
85
86
  this.server = server
86
87
  server.keepAliveTimeout = 90000
@@ -94,6 +95,7 @@ export class OzoneService {
94
95
  async destroy(): Promise<void> {
95
96
  await this.terminator?.terminate()
96
97
  await this.ctx.backgroundQueue.destroy()
98
+ await this.ctx.sequencer.destroy()
97
99
  await this.ctx.db.close()
98
100
  clearInterval(this.dbStatsInterval)
99
101
  }
@@ -30,6 +30,7 @@ import * as ComAtprotoAdminSearchRepos from './types/com/atproto/admin/searchRep
30
30
  import * as ComAtprotoAdminSendEmail from './types/com/atproto/admin/sendEmail'
31
31
  import * as ComAtprotoAdminUpdateAccountEmail from './types/com/atproto/admin/updateAccountEmail'
32
32
  import * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/updateAccountHandle'
33
+ import * as ComAtprotoAdminUpdateAccountPassword from './types/com/atproto/admin/updateAccountPassword'
33
34
  import * as ComAtprotoAdminUpdateCommunicationTemplate from './types/com/atproto/admin/updateCommunicationTemplate'
34
35
  import * as ComAtprotoAdminUpdateSubjectStatus from './types/com/atproto/admin/updateSubjectStatus'
35
36
  import * as ComAtprotoIdentityGetRecommendedDidCredentials from './types/com/atproto/identity/getRecommendedDidCredentials'
@@ -444,6 +445,17 @@ export class ComAtprotoAdminNS {
444
445
  return this._server.xrpc.method(nsid, cfg)
445
446
  }
446
447
 
448
+ updateAccountPassword<AV extends AuthVerifier>(
449
+ cfg: ConfigOf<
450
+ AV,
451
+ ComAtprotoAdminUpdateAccountPassword.Handler<ExtractAuth<AV>>,
452
+ ComAtprotoAdminUpdateAccountPassword.HandlerReqCtx<ExtractAuth<AV>>
453
+ >,
454
+ ) {
455
+ const nsid = 'com.atproto.admin.updateAccountPassword' // @ts-ignore
456
+ return this._server.xrpc.method(nsid, cfg)
457
+ }
458
+
447
459
  updateCommunicationTemplate<AV extends AuthVerifier>(
448
460
  cfg: ConfigOf<
449
461
  AV,
@@ -1863,6 +1863,33 @@ export const schemaDict = {
1863
1863
  },
1864
1864
  },
1865
1865
  },
1866
+ ComAtprotoAdminUpdateAccountPassword: {
1867
+ lexicon: 1,
1868
+ id: 'com.atproto.admin.updateAccountPassword',
1869
+ defs: {
1870
+ main: {
1871
+ type: 'procedure',
1872
+ description:
1873
+ 'Update the password for a user account as an administrator.',
1874
+ input: {
1875
+ encoding: 'application/json',
1876
+ schema: {
1877
+ type: 'object',
1878
+ required: ['did', 'password'],
1879
+ properties: {
1880
+ did: {
1881
+ type: 'string',
1882
+ format: 'did',
1883
+ },
1884
+ password: {
1885
+ type: 'string',
1886
+ },
1887
+ },
1888
+ },
1889
+ },
1890
+ },
1891
+ },
1892
+ },
1866
1893
  ComAtprotoAdminUpdateCommunicationTemplate: {
1867
1894
  lexicon: 1,
1868
1895
  id: 'com.atproto.admin.updateCommunicationTemplate',
@@ -4840,7 +4867,7 @@ export const schemaDict = {
4840
4867
  main: {
4841
4868
  type: 'query',
4842
4869
  description:
4843
- 'Fetch all labels from a labeler created after a certain date. DEPRECATED: use queryLabels or subscribeLabels instead',
4870
+ 'DEPRECATED: use queryLabels or subscribeLabels instead -- Fetch all labels from a labeler created after a certain date.',
4844
4871
  parameters: {
4845
4872
  type: 'params',
4846
4873
  properties: {
@@ -5075,6 +5102,8 @@ export const schemaDict = {
5075
5102
  'lex:app.bsky.actor.defs#feedViewPref',
5076
5103
  'lex:app.bsky.actor.defs#threadViewPref',
5077
5104
  'lex:app.bsky.actor.defs#interestsPref',
5105
+ 'lex:app.bsky.actor.defs#mutedWordsPref',
5106
+ 'lex:app.bsky.actor.defs#hiddenPostsPref',
5078
5107
  ],
5079
5108
  },
5080
5109
  },
@@ -8860,6 +8889,8 @@ export const ids = {
8860
8889
  ComAtprotoAdminSendEmail: 'com.atproto.admin.sendEmail',
8861
8890
  ComAtprotoAdminUpdateAccountEmail: 'com.atproto.admin.updateAccountEmail',
8862
8891
  ComAtprotoAdminUpdateAccountHandle: 'com.atproto.admin.updateAccountHandle',
8892
+ ComAtprotoAdminUpdateAccountPassword:
8893
+ 'com.atproto.admin.updateAccountPassword',
8863
8894
  ComAtprotoAdminUpdateCommunicationTemplate:
8864
8895
  'com.atproto.admin.updateCommunicationTemplate',
8865
8896
  ComAtprotoAdminUpdateSubjectStatus: 'com.atproto.admin.updateSubjectStatus',
@@ -114,6 +114,8 @@ export type Preferences = (
114
114
  | FeedViewPref
115
115
  | ThreadViewPref
116
116
  | InterestsPref
117
+ | MutedWordsPref
118
+ | HiddenPostsPref
117
119
  | { $type: string; [k: string]: unknown }
118
120
  )[]
119
121
 
@@ -0,0 +1,39 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import express from 'express'
5
+ import { ValidationResult, BlobRef } from '@atproto/lexicon'
6
+ import { lexicons } from '../../../../lexicons'
7
+ import { isObj, hasProp } from '../../../../util'
8
+ import { CID } from 'multiformats/cid'
9
+ import { HandlerAuth, HandlerPipeThrough } from '@atproto/xrpc-server'
10
+
11
+ export interface QueryParams {}
12
+
13
+ export interface InputSchema {
14
+ did: string
15
+ password: string
16
+ [k: string]: unknown
17
+ }
18
+
19
+ export interface HandlerInput {
20
+ encoding: 'application/json'
21
+ body: InputSchema
22
+ }
23
+
24
+ export interface HandlerError {
25
+ status: number
26
+ message?: string
27
+ }
28
+
29
+ export type HandlerOutput = HandlerError | void
30
+ export type HandlerReqCtx<HA extends HandlerAuth = never> = {
31
+ auth: HA
32
+ params: QueryParams
33
+ input: HandlerInput
34
+ req: express.Request
35
+ res: express.Response
36
+ }
37
+ export type Handler<HA extends HandlerAuth = never> = (
38
+ ctx: HandlerReqCtx<HA>,
39
+ ) => Promise<HandlerOutput> | HandlerOutput
package/src/logger.ts CHANGED
@@ -3,6 +3,8 @@ import { subsystemLogger } from '@atproto/common'
3
3
 
4
4
  export const dbLogger: ReturnType<typeof subsystemLogger> =
5
5
  subsystemLogger('ozone:db')
6
+ export const seqLogger: ReturnType<typeof subsystemLogger> =
7
+ subsystemLogger('ozone:sequencer')
6
8
  export const httpLogger: ReturnType<typeof subsystemLogger> =
7
9
  subsystemLogger('ozone')
8
10
  export const langLogger: ReturnType<typeof subsystemLogger> =