@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.
- package/CHANGELOG.md +16 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +8 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/proxied.d.ts.map +1 -1
- package/dist/api/proxied.js +13 -0
- package/dist/api/proxied.js.map +1 -1
- package/dist/api/server/getConfig.d.ts.map +1 -1
- package/dist/api/server/getConfig.js +4 -3
- package/dist/api/server/getConfig.js.map +1 -1
- package/dist/api/team/addMember.d.ts +4 -0
- package/dist/api/team/addMember.d.ts.map +1 -0
- package/dist/api/team/addMember.js +38 -0
- package/dist/api/team/addMember.js.map +1 -0
- package/dist/api/team/deleteMember.d.ts +4 -0
- package/dist/api/team/deleteMember.d.ts.map +1 -0
- package/dist/api/team/deleteMember.js +26 -0
- package/dist/api/team/deleteMember.js.map +1 -0
- package/dist/api/team/listMembers.d.ts +4 -0
- package/dist/api/team/listMembers.d.ts.map +1 -0
- package/dist/api/team/listMembers.js +20 -0
- package/dist/api/team/listMembers.js.map +1 -0
- package/dist/api/team/updateMember.d.ts +4 -0
- package/dist/api/team/updateMember.d.ts.map +1 -0
- package/dist/api/team/updateMember.js +40 -0
- package/dist/api/team/updateMember.js.map +1 -0
- package/dist/api/util.d.ts +1 -0
- package/dist/api/util.d.ts.map +1 -1
- package/dist/api/util.js +10 -1
- package/dist/api/util.js.map +1 -1
- package/dist/auth-verifier.d.ts +3 -6
- package/dist/auth-verifier.d.ts.map +1 -1
- package/dist/auth-verifier.js +7 -19
- package/dist/auth-verifier.js.map +1 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +10 -7
- package/dist/config/config.js.map +1 -1
- package/dist/context.d.ts +3 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +7 -3
- package/dist/context.js.map +1 -1
- package/dist/db/migrations/20240521T211332580Z-member.d.ts +4 -0
- package/dist/db/migrations/20240521T211332580Z-member.d.ts.map +1 -0
- package/dist/db/migrations/20240521T211332580Z-member.js +20 -0
- package/dist/db/migrations/20240521T211332580Z-member.js.map +1 -0
- package/dist/db/migrations/index.d.ts +1 -0
- package/dist/db/migrations/index.d.ts.map +1 -1
- package/dist/db/migrations/index.js +2 -1
- package/dist/db/migrations/index.js.map +1 -1
- package/dist/db/schema/index.d.ts +2 -1
- package/dist/db/schema/index.d.ts.map +1 -1
- package/dist/db/schema/member.d.ts +14 -0
- package/dist/db/schema/member.d.ts.map +1 -0
- package/dist/db/schema/member.js +5 -0
- package/dist/db/schema/member.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -1
- package/dist/lexicon/index.d.ts +18 -0
- package/dist/lexicon/index.d.ts.map +1 -1
- package/dist/lexicon/index.js +41 -1
- package/dist/lexicon/index.js.map +1 -1
- package/dist/lexicon/lexicons.d.ts +204 -0
- package/dist/lexicon/lexicons.d.ts.map +1 -1
- package/dist/lexicon/lexicons.js +222 -0
- package/dist/lexicon/lexicons.js.map +1 -1
- package/dist/lexicon/types/tools/ozone/team/addMember.d.ts +40 -0
- package/dist/lexicon/types/tools/ozone/team/addMember.d.ts.map +1 -0
- package/dist/lexicon/types/tools/ozone/team/addMember.js +3 -0
- package/dist/lexicon/types/tools/ozone/team/addMember.js.map +1 -0
- package/dist/lexicon/types/tools/ozone/team/defs.d.ts +24 -0
- package/dist/lexicon/types/tools/ozone/team/defs.d.ts.map +1 -0
- package/dist/lexicon/types/tools/ozone/team/defs.js +22 -0
- package/dist/lexicon/types/tools/ozone/team/defs.js.map +1 -0
- package/dist/lexicon/types/tools/ozone/team/deleteMember.d.ts +30 -0
- package/dist/lexicon/types/tools/ozone/team/deleteMember.d.ts.map +1 -0
- package/dist/lexicon/types/tools/ozone/team/deleteMember.js +3 -0
- package/dist/lexicon/types/tools/ozone/team/deleteMember.js.map +1 -0
- package/dist/lexicon/types/tools/ozone/team/listMembers.d.ts +38 -0
- package/dist/lexicon/types/tools/ozone/team/listMembers.d.ts.map +1 -0
- package/dist/lexicon/types/tools/ozone/team/listMembers.js +3 -0
- package/dist/lexicon/types/tools/ozone/team/listMembers.js.map +1 -0
- package/dist/lexicon/types/tools/ozone/team/updateMember.d.ts +41 -0
- package/dist/lexicon/types/tools/ozone/team/updateMember.d.ts.map +1 -0
- package/dist/lexicon/types/tools/ozone/team/updateMember.js +3 -0
- package/dist/lexicon/types/tools/ozone/team/updateMember.js.map +1 -0
- package/dist/logger.d.ts +2 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/team/index.d.ts +37 -0
- package/dist/team/index.d.ts.map +1 -0
- package/dist/team/index.js +144 -0
- package/dist/team/index.js.map +1 -0
- package/package.json +3 -3
- package/src/api/index.ts +8 -0
- package/src/api/proxied.ts +17 -0
- package/src/api/server/getConfig.ts +4 -4
- package/src/api/team/addMember.ts +46 -0
- package/src/api/team/deleteMember.ts +29 -0
- package/src/api/team/listMembers.ts +20 -0
- package/src/api/team/updateMember.ts +47 -0
- package/src/api/util.ts +15 -0
- package/src/auth-verifier.ts +14 -12
- package/src/config/config.ts +10 -7
- package/src/context.ts +9 -3
- package/src/db/migrations/20240521T211332580Z-member.ts +17 -0
- package/src/db/migrations/index.ts +1 -0
- package/src/db/schema/index.ts +3 -1
- package/src/db/schema/member.ts +19 -0
- package/src/index.ts +36 -0
- package/src/lexicon/index.ts +63 -0
- package/src/lexicon/lexicons.ts +225 -0
- package/src/lexicon/types/tools/ozone/team/addMember.ts +53 -0
- package/src/lexicon/types/tools/ozone/team/defs.ts +42 -0
- package/src/lexicon/types/tools/ozone/team/deleteMember.ts +39 -0
- package/src/lexicon/types/tools/ozone/team/listMembers.ts +48 -0
- package/src/lexicon/types/tools/ozone/team/updateMember.ts +54 -0
- package/src/team/index.ts +213 -0
- package/tests/__snapshots__/team.test.ts.snap +664 -0
- package/tests/get-config.test.ts +3 -4
- 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])
|
package/src/auth-verifier.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
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',
|
package/src/config/config.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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'
|
package/src/db/schema/index.ts
CHANGED
|
@@ -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(
|
package/src/lexicon/index.ts
CHANGED
|
@@ -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
|