@atproto/ozone 0.1.24 → 0.1.26

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 (121) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/api/index.d.ts.map +1 -1
  3. package/dist/api/index.js +8 -0
  4. package/dist/api/index.js.map +1 -1
  5. package/dist/api/proxied.d.ts.map +1 -1
  6. package/dist/api/proxied.js +13 -0
  7. package/dist/api/proxied.js.map +1 -1
  8. package/dist/api/server/getConfig.d.ts.map +1 -1
  9. package/dist/api/server/getConfig.js +4 -3
  10. package/dist/api/server/getConfig.js.map +1 -1
  11. package/dist/api/team/addMember.d.ts +4 -0
  12. package/dist/api/team/addMember.d.ts.map +1 -0
  13. package/dist/api/team/addMember.js +38 -0
  14. package/dist/api/team/addMember.js.map +1 -0
  15. package/dist/api/team/deleteMember.d.ts +4 -0
  16. package/dist/api/team/deleteMember.d.ts.map +1 -0
  17. package/dist/api/team/deleteMember.js +26 -0
  18. package/dist/api/team/deleteMember.js.map +1 -0
  19. package/dist/api/team/listMembers.d.ts +4 -0
  20. package/dist/api/team/listMembers.d.ts.map +1 -0
  21. package/dist/api/team/listMembers.js +20 -0
  22. package/dist/api/team/listMembers.js.map +1 -0
  23. package/dist/api/team/updateMember.d.ts +4 -0
  24. package/dist/api/team/updateMember.d.ts.map +1 -0
  25. package/dist/api/team/updateMember.js +40 -0
  26. package/dist/api/team/updateMember.js.map +1 -0
  27. package/dist/api/util.d.ts +1 -0
  28. package/dist/api/util.d.ts.map +1 -1
  29. package/dist/api/util.js +10 -1
  30. package/dist/api/util.js.map +1 -1
  31. package/dist/auth-verifier.d.ts +3 -6
  32. package/dist/auth-verifier.d.ts.map +1 -1
  33. package/dist/auth-verifier.js +7 -19
  34. package/dist/auth-verifier.js.map +1 -1
  35. package/dist/config/config.d.ts.map +1 -1
  36. package/dist/config/config.js +10 -7
  37. package/dist/config/config.js.map +1 -1
  38. package/dist/context.d.ts +3 -0
  39. package/dist/context.d.ts.map +1 -1
  40. package/dist/context.js +7 -3
  41. package/dist/context.js.map +1 -1
  42. package/dist/db/migrations/20240521T211332580Z-member.d.ts +4 -0
  43. package/dist/db/migrations/20240521T211332580Z-member.d.ts.map +1 -0
  44. package/dist/db/migrations/20240521T211332580Z-member.js +20 -0
  45. package/dist/db/migrations/20240521T211332580Z-member.js.map +1 -0
  46. package/dist/db/migrations/index.d.ts +1 -0
  47. package/dist/db/migrations/index.d.ts.map +1 -1
  48. package/dist/db/migrations/index.js +2 -1
  49. package/dist/db/migrations/index.js.map +1 -1
  50. package/dist/db/schema/index.d.ts +2 -1
  51. package/dist/db/schema/index.d.ts.map +1 -1
  52. package/dist/db/schema/member.d.ts +14 -0
  53. package/dist/db/schema/member.d.ts.map +1 -0
  54. package/dist/db/schema/member.js +5 -0
  55. package/dist/db/schema/member.js.map +1 -0
  56. package/dist/index.d.ts +1 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +25 -0
  59. package/dist/index.js.map +1 -1
  60. package/dist/lexicon/index.d.ts +18 -0
  61. package/dist/lexicon/index.d.ts.map +1 -1
  62. package/dist/lexicon/index.js +41 -1
  63. package/dist/lexicon/index.js.map +1 -1
  64. package/dist/lexicon/lexicons.d.ts +204 -0
  65. package/dist/lexicon/lexicons.d.ts.map +1 -1
  66. package/dist/lexicon/lexicons.js +222 -0
  67. package/dist/lexicon/lexicons.js.map +1 -1
  68. package/dist/lexicon/types/tools/ozone/team/addMember.d.ts +40 -0
  69. package/dist/lexicon/types/tools/ozone/team/addMember.d.ts.map +1 -0
  70. package/dist/lexicon/types/tools/ozone/team/addMember.js +3 -0
  71. package/dist/lexicon/types/tools/ozone/team/addMember.js.map +1 -0
  72. package/dist/lexicon/types/tools/ozone/team/defs.d.ts +24 -0
  73. package/dist/lexicon/types/tools/ozone/team/defs.d.ts.map +1 -0
  74. package/dist/lexicon/types/tools/ozone/team/defs.js +22 -0
  75. package/dist/lexicon/types/tools/ozone/team/defs.js.map +1 -0
  76. package/dist/lexicon/types/tools/ozone/team/deleteMember.d.ts +30 -0
  77. package/dist/lexicon/types/tools/ozone/team/deleteMember.d.ts.map +1 -0
  78. package/dist/lexicon/types/tools/ozone/team/deleteMember.js +3 -0
  79. package/dist/lexicon/types/tools/ozone/team/deleteMember.js.map +1 -0
  80. package/dist/lexicon/types/tools/ozone/team/listMembers.d.ts +38 -0
  81. package/dist/lexicon/types/tools/ozone/team/listMembers.d.ts.map +1 -0
  82. package/dist/lexicon/types/tools/ozone/team/listMembers.js +3 -0
  83. package/dist/lexicon/types/tools/ozone/team/listMembers.js.map +1 -0
  84. package/dist/lexicon/types/tools/ozone/team/updateMember.d.ts +41 -0
  85. package/dist/lexicon/types/tools/ozone/team/updateMember.d.ts.map +1 -0
  86. package/dist/lexicon/types/tools/ozone/team/updateMember.js +3 -0
  87. package/dist/lexicon/types/tools/ozone/team/updateMember.js.map +1 -0
  88. package/dist/logger.d.ts +2 -1
  89. package/dist/logger.d.ts.map +1 -1
  90. package/dist/team/index.d.ts +37 -0
  91. package/dist/team/index.d.ts.map +1 -0
  92. package/dist/team/index.js +144 -0
  93. package/dist/team/index.js.map +1 -0
  94. package/package.json +3 -3
  95. package/src/api/index.ts +8 -0
  96. package/src/api/proxied.ts +17 -0
  97. package/src/api/server/getConfig.ts +4 -4
  98. package/src/api/team/addMember.ts +46 -0
  99. package/src/api/team/deleteMember.ts +29 -0
  100. package/src/api/team/listMembers.ts +20 -0
  101. package/src/api/team/updateMember.ts +47 -0
  102. package/src/api/util.ts +15 -0
  103. package/src/auth-verifier.ts +14 -12
  104. package/src/config/config.ts +10 -7
  105. package/src/context.ts +9 -3
  106. package/src/db/migrations/20240521T211332580Z-member.ts +17 -0
  107. package/src/db/migrations/index.ts +1 -0
  108. package/src/db/schema/index.ts +3 -1
  109. package/src/db/schema/member.ts +19 -0
  110. package/src/index.ts +36 -0
  111. package/src/lexicon/index.ts +63 -0
  112. package/src/lexicon/lexicons.ts +225 -0
  113. package/src/lexicon/types/tools/ozone/team/addMember.ts +53 -0
  114. package/src/lexicon/types/tools/ozone/team/defs.ts +42 -0
  115. package/src/lexicon/types/tools/ozone/team/deleteMember.ts +39 -0
  116. package/src/lexicon/types/tools/ozone/team/listMembers.ts +48 -0
  117. package/src/lexicon/types/tools/ozone/team/updateMember.ts +54 -0
  118. package/src/team/index.ts +213 -0
  119. package/tests/__snapshots__/team.test.ts.snap +664 -0
  120. package/tests/get-config.test.ts +3 -4
  121. package/tests/team.test.ts +163 -0
@@ -0,0 +1,29 @@
1
+ import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
2
+ import { Server } from '../../lexicon'
3
+ import AppContext from '../../context'
4
+
5
+ export default function (server: Server, ctx: AppContext) {
6
+ server.tools.ozone.team.deleteMember({
7
+ auth: ctx.authVerifier.modOrAdminToken,
8
+ handler: async ({ input, auth }) => {
9
+ const access = auth.credentials
10
+ const db = ctx.db
11
+ const { did } = input.body
12
+
13
+ if (!access.isAdmin) {
14
+ throw new AuthRequiredError('Must be an admin to delete a member')
15
+ }
16
+ if ('did' in auth.credentials && did === auth.credentials.did) {
17
+ throw new InvalidRequestError(
18
+ 'You can not delete yourself from the team',
19
+ 'CannotDeleteSelf',
20
+ )
21
+ }
22
+ await db.transaction(async (dbTxn) => {
23
+ const teamService = ctx.teamService(dbTxn)
24
+ await teamService.assertCanDelete(did)
25
+ await teamService.delete(did)
26
+ })
27
+ },
28
+ })
29
+ }
@@ -0,0 +1,20 @@
1
+ import { Server } from '../../lexicon'
2
+ import AppContext from '../../context'
3
+
4
+ export default function (server: Server, ctx: AppContext) {
5
+ server.tools.ozone.team.listMembers({
6
+ auth: ctx.authVerifier.modOrAdminToken,
7
+ handler: async ({ params }) => {
8
+ const teamService = ctx.teamService(ctx.db)
9
+ const { members, cursor } = await teamService.list(params)
10
+
11
+ return {
12
+ encoding: 'application/json',
13
+ body: {
14
+ cursor,
15
+ members: await teamService.view(members, ctx),
16
+ },
17
+ }
18
+ },
19
+ })
20
+ }
@@ -0,0 +1,47 @@
1
+ import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
2
+ import { Server } from '../../lexicon'
3
+ import AppContext from '../../context'
4
+ import { getMemberRole } from '../util'
5
+
6
+ export default function (server: Server, ctx: AppContext) {
7
+ server.tools.ozone.team.updateMember({
8
+ auth: ctx.authVerifier.modOrAdminToken,
9
+ handler: async ({ input, auth }) => {
10
+ const access = auth.credentials
11
+ const db = ctx.db
12
+ const { did, role, ...rest } = input.body
13
+
14
+ if (!access.isAdmin) {
15
+ throw new AuthRequiredError('Must be an admin to update a member')
16
+ }
17
+
18
+ if (did === ctx.cfg.service.did) {
19
+ throw new InvalidRequestError('Can not update service owner')
20
+ }
21
+
22
+ const updatedMember = await db.transaction(async (dbTxn) => {
23
+ const teamService = ctx.teamService(dbTxn)
24
+
25
+ const memberExists = await teamService.doesMemberExist(did)
26
+
27
+ if (!memberExists) {
28
+ throw new InvalidRequestError('member not found', 'MemberNotFound')
29
+ }
30
+
31
+ const updated = await teamService.update(did, {
32
+ ...rest,
33
+ ...(role ? { role: getMemberRole(role) } : {}),
34
+ lastUpdatedBy:
35
+ access.type === 'admin_token' ? 'admin_token' : access.iss,
36
+ })
37
+ const memberView = await teamService.view([updated], ctx)
38
+ return memberView[0]
39
+ })
40
+
41
+ return {
42
+ encoding: 'application/json',
43
+ body: updatedMember,
44
+ }
45
+ },
46
+ })
47
+ }
package/src/api/util.ts CHANGED
@@ -20,6 +20,12 @@ import {
20
20
  import { ModerationEvent } from '../db/schema/moderation_event'
21
21
  import { ModerationSubjectStatusRow } from '../mod-service/types'
22
22
  import AppContext from '../context'
23
+ import { Member } from '../db/schema/member'
24
+ import {
25
+ ROLEADMIN,
26
+ ROLEMODERATOR,
27
+ ROLETRIAGE,
28
+ } from '../lexicon/types/tools/ozone/team/defs'
23
29
 
24
30
  export const getPdsAccountInfo = async (
25
31
  ctx: AppContext,
@@ -122,3 +128,12 @@ const eventTypes = new Set([
122
128
  'tools.ozone.moderation.defs#modEventTag',
123
129
  'tools.ozone.moderation.defs#modEventDivert',
124
130
  ])
131
+
132
+ export const getMemberRole = (role: string) => {
133
+ if (memberRoles.has(role)) {
134
+ return role as Member['role']
135
+ }
136
+ throw new InvalidRequestError('Invalid member role')
137
+ }
138
+
139
+ const memberRoles = new Set([ROLEADMIN, ROLEMODERATOR, ROLETRIAGE])
@@ -2,6 +2,7 @@ import express from 'express'
2
2
  import * as ui8 from 'uint8arrays'
3
3
  import { IdResolver } from '@atproto/identity'
4
4
  import { AuthRequiredError, verifyJwt } from '@atproto/xrpc-server'
5
+ import { TeamService } from './team'
5
6
 
6
7
  type ReqCtx = {
7
8
  req: express.Request
@@ -47,17 +48,13 @@ type NullOutput = {
47
48
 
48
49
  export type AuthVerifierOpts = {
49
50
  serviceDid: string
50
- admins: string[]
51
- moderators: string[]
52
- triage: string[]
53
51
  adminPassword: string
52
+ teamService: TeamService
54
53
  }
55
54
 
56
55
  export class AuthVerifier {
57
56
  serviceDid: string
58
- admins: string[]
59
- moderators: string[]
60
- triage: string[]
57
+ teamService: TeamService
61
58
  private adminPassword: string
62
59
 
63
60
  constructor(
@@ -65,10 +62,8 @@ export class AuthVerifier {
65
62
  opts: AuthVerifierOpts,
66
63
  ) {
67
64
  this.serviceDid = opts.serviceDid
68
- this.admins = opts.admins
69
- this.moderators = opts.moderators
70
- this.triage = opts.triage
71
65
  this.adminPassword = opts.adminPassword
66
+ this.teamService = opts.teamService
72
67
  }
73
68
 
74
69
  modOrAdminToken = async (
@@ -113,9 +108,16 @@ export class AuthVerifier {
113
108
  }
114
109
  const payload = await verifyJwt(jwtStr, this.serviceDid, getSigningKey)
115
110
  const iss = payload.iss
116
- const isAdmin = this.admins.includes(iss)
117
- const isModerator = isAdmin || this.moderators.includes(iss)
118
- const isTriage = isModerator || this.triage.includes(iss)
111
+
112
+ const member = await this.teamService.getMember(iss)
113
+
114
+ if (member?.disabled) {
115
+ throw new AuthRequiredError('member is disabled', 'MemberDisabled')
116
+ }
117
+
118
+ const { isAdmin, isModerator, isTriage } =
119
+ this.teamService.getMemberRole(member)
120
+
119
121
  return {
120
122
  credentials: {
121
123
  type: 'standard',
@@ -6,8 +6,8 @@ import { OzoneEnvironment } from './env'
6
6
 
7
7
  export const envToCfg = (env: OzoneEnvironment): OzoneConfig => {
8
8
  const port = env.port ?? 3000
9
- assert(env.publicUrl)
10
- assert(env.serverDid)
9
+ assert(env.publicUrl, 'publicUrl is required')
10
+ assert(env.serverDid, 'serverDid is required')
11
11
  const serviceCfg: OzoneConfig['service'] = {
12
12
  port,
13
13
  publicUrl: env.publicUrl,
@@ -16,7 +16,7 @@ export const envToCfg = (env: OzoneEnvironment): OzoneConfig => {
16
16
  devMode: env.devMode,
17
17
  }
18
18
 
19
- assert(env.dbPostgresUrl)
19
+ assert(env.dbPostgresUrl, 'dbPostgresUrl is required')
20
20
  const dbCfg: OzoneConfig['db'] = {
21
21
  postgresUrl: env.dbPostgresUrl,
22
22
  postgresSchema: env.dbPostgresSchema,
@@ -25,7 +25,8 @@ export const envToCfg = (env: OzoneEnvironment): OzoneConfig => {
25
25
  poolIdleTimeoutMs: env.dbPoolIdleTimeoutMs,
26
26
  }
27
27
 
28
- assert(env.appviewUrl && env.appviewDid)
28
+ assert(env.appviewUrl, 'appviewUrl is required')
29
+ assert(env.appviewDid, 'appviewDid is required')
29
30
  const appviewCfg: OzoneConfig['appview'] = {
30
31
  url: env.appviewUrl,
31
32
  did: env.appviewDid,
@@ -34,7 +35,8 @@ export const envToCfg = (env: OzoneEnvironment): OzoneConfig => {
34
35
 
35
36
  let pdsCfg: OzoneConfig['pds'] = null
36
37
  if (env.pdsUrl || env.pdsDid) {
37
- assert(env.pdsUrl && env.pdsDid)
38
+ assert(env.pdsUrl, 'pdsUrl is required')
39
+ assert(env.pdsDid, 'pdsDid is required')
38
40
  pdsCfg = {
39
41
  url: env.pdsUrl,
40
42
  did: env.pdsDid,
@@ -43,7 +45,8 @@ export const envToCfg = (env: OzoneEnvironment): OzoneConfig => {
43
45
 
44
46
  let chatCfg: OzoneConfig['chat'] = null
45
47
  if (env.chatUrl || env.chatDid) {
46
- assert(env.chatUrl && env.chatDid)
48
+ assert(env.chatUrl, 'chatUrl is required when chatDid is provided')
49
+ assert(env.chatDid, 'chatDid is required when chatUrl is provided')
47
50
  chatCfg = {
48
51
  url: env.chatUrl,
49
52
  did: env.chatDid,
@@ -54,7 +57,7 @@ export const envToCfg = (env: OzoneEnvironment): OzoneConfig => {
54
57
  paths: env.cdnPaths,
55
58
  }
56
59
 
57
- assert(env.didPlcUrl)
60
+ assert(env.didPlcUrl, 'didPlcUrl is required')
58
61
  const identityCfg: OzoneConfig['identity'] = {
59
62
  plcUrl: env.didPlcUrl,
60
63
  }
package/src/context.ts CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  import { BlobDiverter } from './daemon/blob-diverter'
19
19
  import { AuthVerifier } from './auth-verifier'
20
20
  import { ImageInvalidator } from './image-invalidator'
21
+ import { TeamService, TeamServiceCreator } from './team'
21
22
  import {
22
23
  defaultLabelerHeader,
23
24
  getSigningKeyId,
@@ -31,6 +32,7 @@ export type AppContextOptions = {
31
32
  cfg: OzoneConfig
32
33
  modService: ModerationServiceCreator
33
34
  communicationTemplateService: CommunicationTemplateServiceCreator
35
+ teamService: TeamServiceCreator
34
36
  appviewAgent: AtpAgent
35
37
  pdsAgent: AtpAgent | undefined
36
38
  chatAgent: AtpAgent | undefined
@@ -107,15 +109,14 @@ export class AppContext {
107
109
  )
108
110
 
109
111
  const communicationTemplateService = CommunicationTemplateService.creator()
112
+ const teamService = TeamService.creator()
110
113
 
111
114
  const sequencer = new Sequencer(modService(db))
112
115
 
113
116
  const authVerifier = new AuthVerifier(idResolver, {
114
117
  serviceDid: cfg.service.did,
115
- admins: cfg.access.admins,
116
- moderators: cfg.access.moderators,
117
- triage: cfg.access.triage,
118
118
  adminPassword: secrets.adminPassword,
119
+ teamService: teamService(db),
119
120
  })
120
121
 
121
122
  return new AppContext(
@@ -124,6 +125,7 @@ export class AppContext {
124
125
  cfg,
125
126
  modService,
126
127
  communicationTemplateService,
128
+ teamService,
127
129
  appviewAgent,
128
130
  pdsAgent,
129
131
  chatAgent,
@@ -168,6 +170,10 @@ export class AppContext {
168
170
  return this.opts.communicationTemplateService
169
171
  }
170
172
 
173
+ get teamService(): TeamServiceCreator {
174
+ return this.opts.teamService
175
+ }
176
+
171
177
  get appviewAgent(): AtpAgent {
172
178
  return this.opts.appviewAgent
173
179
  }
@@ -0,0 +1,17 @@
1
+ import { Kysely } from 'kysely'
2
+
3
+ export async function up(db: Kysely<unknown>): Promise<void> {
4
+ await db.schema
5
+ .createTable('member')
6
+ .addColumn('did', 'varchar', (col) => col.primaryKey())
7
+ .addColumn('role', 'varchar', (col) => col.notNull())
8
+ .addColumn('disabled', 'boolean', (col) => col.defaultTo(false).notNull())
9
+ .addColumn('createdAt', 'timestamptz', (col) => col.notNull())
10
+ .addColumn('updatedAt', 'timestamptz', (col) => col.notNull())
11
+ .addColumn('lastUpdatedBy', 'varchar', (col) => col.notNull())
12
+ .execute()
13
+ }
14
+
15
+ export async function down(db: Kysely<unknown>): Promise<void> {
16
+ await db.schema.dropTable('member')
17
+ }
@@ -9,3 +9,4 @@ export * as _20240208T213404429Z from './20240208T213404429Z-add-tags-column-to-
9
9
  export * as _20240228T003647759Z from './20240228T003647759Z-add-label-sigs'
10
10
  export * as _20240408T192432676Z from './20240408T192432676Z-mute-reporting'
11
11
  export * as _20240506T225055595Z from './20240506T225055595Z-message-subject'
12
+ export * as _20240430T211332580Z from './20240521T211332580Z-member'
@@ -7,6 +7,7 @@ import * as blobPushEvent from './blob_push_event'
7
7
  import * as label from './label'
8
8
  import * as signingKey from './signing_key'
9
9
  import * as communicationTemplate from './communication_template'
10
+ import * as member from './member'
10
11
 
11
12
  export type DatabaseSchemaType = modEvent.PartialDB &
12
13
  modSubjectStatus.PartialDB &
@@ -15,7 +16,8 @@ export type DatabaseSchemaType = modEvent.PartialDB &
15
16
  repoPushEvent.PartialDB &
16
17
  recordPushEvent.PartialDB &
17
18
  blobPushEvent.PartialDB &
18
- communicationTemplate.PartialDB
19
+ communicationTemplate.PartialDB &
20
+ member.PartialDB
19
21
 
20
22
  export type DatabaseSchema = Kysely<DatabaseSchemaType>
21
23
 
@@ -0,0 +1,19 @@
1
+ import { Generated } from 'kysely'
2
+
3
+ export const memberTableName = 'member'
4
+
5
+ export interface Member {
6
+ did: string
7
+ role:
8
+ | 'tools.ozone.team.defs#roleAdmin'
9
+ | 'tools.ozone.team.defs#roleTriage'
10
+ | 'tools.ozone.team.defs#roleModerator'
11
+ disabled: Generated<boolean>
12
+ createdAt: Date
13
+ updatedAt: Date
14
+ lastUpdatedBy: string
15
+ }
16
+
17
+ export type PartialDB = {
18
+ [memberTableName]: Member
19
+ }
package/src/index.ts CHANGED
@@ -12,6 +12,7 @@ import { dbLogger, loggerMiddleware } from './logger'
12
12
  import { OzoneConfig, OzoneSecrets } from './config'
13
13
  import { createServer } from './lexicon'
14
14
  import AppContext, { AppContextOptions } from './context'
15
+ import { Member } from './db/schema/member'
15
16
 
16
17
  export * from './config'
17
18
  export { type ImageInvalidator } from './image-invalidator'
@@ -64,10 +65,45 @@ export class OzoneService {
64
65
  return new OzoneService({ ctx, app })
65
66
  }
66
67
 
68
+ async seedInitialMembers() {
69
+ const members: Array<{ role: Member['role']; did: string }> = []
70
+ this.ctx.cfg.access.admins.forEach((did) =>
71
+ members.push({
72
+ role: 'tools.ozone.team.defs#roleAdmin',
73
+ did,
74
+ }),
75
+ )
76
+ this.ctx.cfg.access.triage.forEach((did) =>
77
+ members.push({
78
+ role: 'tools.ozone.team.defs#roleTriage',
79
+ did,
80
+ }),
81
+ )
82
+ this.ctx.cfg.access.moderators.forEach((did) =>
83
+ members.push({
84
+ role: 'tools.ozone.team.defs#roleModerator',
85
+ did,
86
+ }),
87
+ )
88
+
89
+ for (const member of members) {
90
+ const service = this.ctx.teamService(this.ctx.db)
91
+ await service.upsert({
92
+ ...member,
93
+ lastUpdatedBy: this.ctx.cfg.service.did,
94
+ })
95
+ }
96
+ }
97
+
67
98
  async start(): Promise<http.Server> {
68
99
  if (this.dbStatsInterval) {
69
100
  throw new Error(`${this.constructor.name} already started`)
70
101
  }
102
+
103
+ // Any moderator that are configured via env var may not exist in the database
104
+ // so we need to sync them from env var to the database
105
+ await this.seedInitialMembers()
106
+
71
107
  const { db, backgroundQueue } = this.ctx
72
108
  this.dbStatsInterval = setInterval(() => {
73
109
  dbLogger.info(
@@ -163,6 +163,10 @@ import * as ToolsOzoneModerationQueryEvents from './types/tools/ozone/moderation
163
163
  import * as ToolsOzoneModerationQueryStatuses from './types/tools/ozone/moderation/queryStatuses'
164
164
  import * as ToolsOzoneModerationSearchRepos from './types/tools/ozone/moderation/searchRepos'
165
165
  import * as ToolsOzoneServerGetConfig from './types/tools/ozone/server/getConfig'
166
+ import * as ToolsOzoneTeamAddMember from './types/tools/ozone/team/addMember'
167
+ import * as ToolsOzoneTeamDeleteMember from './types/tools/ozone/team/deleteMember'
168
+ import * as ToolsOzoneTeamListMembers from './types/tools/ozone/team/listMembers'
169
+ import * as ToolsOzoneTeamUpdateMember from './types/tools/ozone/team/updateMember'
166
170
 
167
171
  export const COM_ATPROTO_MODERATION = {
168
172
  DefsReasonSpam: 'com.atproto.moderation.defs#reasonSpam',
@@ -197,6 +201,11 @@ export const TOOLS_OZONE_MODERATION = {
197
201
  DefsReviewClosed: 'tools.ozone.moderation.defs#reviewClosed',
198
202
  DefsReviewNone: 'tools.ozone.moderation.defs#reviewNone',
199
203
  }
204
+ export const TOOLS_OZONE_TEAM = {
205
+ DefsRoleAdmin: 'tools.ozone.team.defs#roleAdmin',
206
+ DefsRoleModerator: 'tools.ozone.team.defs#roleModerator',
207
+ DefsRoleTriage: 'tools.ozone.team.defs#roleTriage',
208
+ }
200
209
 
201
210
  export function createServer(options?: XrpcOptions): Server {
202
211
  return new Server(options)
@@ -2043,12 +2052,14 @@ export class ToolsOzoneNS {
2043
2052
  communication: ToolsOzoneCommunicationNS
2044
2053
  moderation: ToolsOzoneModerationNS
2045
2054
  server: ToolsOzoneServerNS
2055
+ team: ToolsOzoneTeamNS
2046
2056
 
2047
2057
  constructor(server: Server) {
2048
2058
  this._server = server
2049
2059
  this.communication = new ToolsOzoneCommunicationNS(server)
2050
2060
  this.moderation = new ToolsOzoneModerationNS(server)
2051
2061
  this.server = new ToolsOzoneServerNS(server)
2062
+ this.team = new ToolsOzoneTeamNS(server)
2052
2063
  }
2053
2064
  }
2054
2065
 
@@ -2208,6 +2219,58 @@ export class ToolsOzoneServerNS {
2208
2219
  }
2209
2220
  }
2210
2221
 
2222
+ export class ToolsOzoneTeamNS {
2223
+ _server: Server
2224
+
2225
+ constructor(server: Server) {
2226
+ this._server = server
2227
+ }
2228
+
2229
+ addMember<AV extends AuthVerifier>(
2230
+ cfg: ConfigOf<
2231
+ AV,
2232
+ ToolsOzoneTeamAddMember.Handler<ExtractAuth<AV>>,
2233
+ ToolsOzoneTeamAddMember.HandlerReqCtx<ExtractAuth<AV>>
2234
+ >,
2235
+ ) {
2236
+ const nsid = 'tools.ozone.team.addMember' // @ts-ignore
2237
+ return this._server.xrpc.method(nsid, cfg)
2238
+ }
2239
+
2240
+ deleteMember<AV extends AuthVerifier>(
2241
+ cfg: ConfigOf<
2242
+ AV,
2243
+ ToolsOzoneTeamDeleteMember.Handler<ExtractAuth<AV>>,
2244
+ ToolsOzoneTeamDeleteMember.HandlerReqCtx<ExtractAuth<AV>>
2245
+ >,
2246
+ ) {
2247
+ const nsid = 'tools.ozone.team.deleteMember' // @ts-ignore
2248
+ return this._server.xrpc.method(nsid, cfg)
2249
+ }
2250
+
2251
+ listMembers<AV extends AuthVerifier>(
2252
+ cfg: ConfigOf<
2253
+ AV,
2254
+ ToolsOzoneTeamListMembers.Handler<ExtractAuth<AV>>,
2255
+ ToolsOzoneTeamListMembers.HandlerReqCtx<ExtractAuth<AV>>
2256
+ >,
2257
+ ) {
2258
+ const nsid = 'tools.ozone.team.listMembers' // @ts-ignore
2259
+ return this._server.xrpc.method(nsid, cfg)
2260
+ }
2261
+
2262
+ updateMember<AV extends AuthVerifier>(
2263
+ cfg: ConfigOf<
2264
+ AV,
2265
+ ToolsOzoneTeamUpdateMember.Handler<ExtractAuth<AV>>,
2266
+ ToolsOzoneTeamUpdateMember.HandlerReqCtx<ExtractAuth<AV>>
2267
+ >,
2268
+ ) {
2269
+ const nsid = 'tools.ozone.team.updateMember' // @ts-ignore
2270
+ return this._server.xrpc.method(nsid, cfg)
2271
+ }
2272
+ }
2273
+
2211
2274
  type SharedRateLimitOpts<T> = {
2212
2275
  name: string
2213
2276
  calcKey?: (ctx: T) => string