Skip to content

Commit

Permalink
✨ Ozone team member manager (bluesky-social#2460)
Browse files Browse the repository at this point in the history
* 🚧 Proposal for moderator manager lexicons

* ✨ CRUD for moderator management works

* ✨ Add profile view to moderator user list

* ✨ Seed mod users from env var to db

* ✅ Adjust tests

* ✅ Update snapshots

* ✨ Fix type and lexicon token issues

* ✨ Add pagination to listUsers

* ✨ Use sort order in pagination

* 📝 Change error name

* ✅ Update snapshots

* ✅ seed mods in sync to avoid re-order

* 🧹 Remove unnecessary import

* 🐛 Re-run codegen

* 🧹 Some cleanup

* 🧹 Cleanup unnecessary properties on auth-verifier

* ✨ Rename terminologies from moderator to team and member

* 🧹 Cleanup

* ✨ Allow admin tokens to update/add member

* ✨ Delete members in transaction

* ✨ Use db transactions and move profile hydration to the service layer

* ✅ Add test for addMember endpoint

* 🐛 wait on adding admin DID

* ✨ Do not allow updating/deleting service owner and always give service owner admin access

* 🧹 Cleanup

* ✨ Make timestamp columns non null

* 🧹 Cleanup

* ✨ Update mod role definition in getConfig

* ✨ Deletion and update guards

* 🐛 don't prefill service did

* 📝 Add changeset
  • Loading branch information
foysalit authored Jun 18, 2024
1 parent 5f45e7d commit 3ad0519
Show file tree
Hide file tree
Showing 50 changed files with 3,097 additions and 32 deletions.
8 changes: 8 additions & 0 deletions .changeset/lovely-eagles-double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@atproto/dev-env": patch
"@atproto/ozone": patch
"@atproto/api": patch
"@atproto/pds": patch
---

Add DB backed team member management for ozone
41 changes: 41 additions & 0 deletions lexicons/tools/ozone/team/addMember.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"lexicon": 1,
"id": "tools.ozone.team.addMember",
"defs": {
"main": {
"type": "procedure",
"description": "Add a member to the ozone team. Requires admin role.",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["did", "role"],
"properties": {
"did": { "type": "string", "format": "did" },
"role": {
"type": "string",
"knownValues": [
"tools.ozone.team.defs#roleAdmin",
"tools.ozone.team.defs#roleModerator",
"tools.ozone.team.defs#roleTriage"
]
}
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "ref",
"ref": "tools.ozone.team.defs#member"
}
},
"errors": [
{
"name": "MemberAlreadyExists",
"description": "Member already exists in the team."
}
]
}
}
}
37 changes: 37 additions & 0 deletions lexicons/tools/ozone/team/defs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"lexicon": 1,
"id": "tools.ozone.team.defs",
"defs": {
"member": {
"type": "object",
"required": ["did", "role"],
"properties": {
"did": { "type": "string", "format": "did" },
"disabled": { "type": "boolean" },
"profile": {
"type": "ref",
"ref": "app.bsky.actor.defs#profileViewDetailed"
},
"createdAt": { "type": "string", "format": "datetime" },
"updatedAt": { "type": "string", "format": "datetime" },
"lastUpdatedBy": { "type": "string" },
"role": {
"type": "string",
"knownValues": ["#roleAdmin", "#roleModerator", "#roleTriage"]
}
}
},
"roleAdmin": {
"type": "token",
"description": "Admin role. Highest level of access, can perform all actions."
},
"roleModerator": {
"type": "token",
"description": "Moderator role. Can perform most actions."
},
"roleTriage": {
"type": "token",
"description": "Triage role. Mostly intended for monitoring and escalating issues."
}
}
}
30 changes: 30 additions & 0 deletions lexicons/tools/ozone/team/deleteMember.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"lexicon": 1,
"id": "tools.ozone.team.deleteMember",
"defs": {
"main": {
"type": "procedure",
"description": "Delete a member from ozone team. Requires admin role.",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["did"],
"properties": {
"did": { "type": "string", "format": "did" }
}
}
},
"errors": [
{
"name": "MemberNotFound",
"description": "The member being deleted does not exist"
},
{
"name": "CannotDeleteSelf",
"description": "You can not delete yourself from the team"
}
]
}
}
}
39 changes: 39 additions & 0 deletions lexicons/tools/ozone/team/listMembers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"lexicon": 1,
"id": "tools.ozone.team.listMembers",
"defs": {
"main": {
"type": "query",
"description": "List all members with access to the ozone service.",
"parameters": {
"type": "params",
"properties": {
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 50
},
"cursor": { "type": "string" }
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["members"],
"properties": {
"cursor": { "type": "string" },
"members": {
"type": "array",
"items": {
"type": "ref",
"ref": "tools.ozone.team.defs#member"
}
}
}
}
}
}
}
}
42 changes: 42 additions & 0 deletions lexicons/tools/ozone/team/updateMember.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"lexicon": 1,
"id": "tools.ozone.team.updateMember",
"defs": {
"main": {
"type": "procedure",
"description": "Update a member in the ozone service. Requires admin role.",
"input": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["did"],
"properties": {
"did": { "type": "string", "format": "did" },
"disabled": { "type": "boolean" },
"role": {
"type": "string",
"knownValues": [
"tools.ozone.team.defs#roleAdmin",
"tools.ozone.team.defs#roleModerator",
"tools.ozone.team.defs#roleTriage"
]
}
}
}
},
"output": {
"encoding": "application/json",
"schema": {
"type": "ref",
"ref": "tools.ozone.team.defs#member"
}
},
"errors": [
{
"name": "MemberNotFound",
"description": "The member being updated does not exist in the team"
}
]
}
}
}
69 changes: 69 additions & 0 deletions packages/api/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ import * as ToolsOzoneModerationQueryEvents from './types/tools/ozone/moderation
import * as ToolsOzoneModerationQueryStatuses from './types/tools/ozone/moderation/queryStatuses'
import * as ToolsOzoneModerationSearchRepos from './types/tools/ozone/moderation/searchRepos'
import * as ToolsOzoneServerGetConfig from './types/tools/ozone/server/getConfig'
import * as ToolsOzoneTeamAddMember from './types/tools/ozone/team/addMember'
import * as ToolsOzoneTeamDefs from './types/tools/ozone/team/defs'
import * as ToolsOzoneTeamDeleteMember from './types/tools/ozone/team/deleteMember'
import * as ToolsOzoneTeamListMembers from './types/tools/ozone/team/listMembers'
import * as ToolsOzoneTeamUpdateMember from './types/tools/ozone/team/updateMember'

export * as ComAtprotoAdminDefs from './types/com/atproto/admin/defs'
export * as ComAtprotoAdminDeleteAccount from './types/com/atproto/admin/deleteAccount'
Expand Down Expand Up @@ -380,6 +385,11 @@ export * as ToolsOzoneModerationQueryEvents from './types/tools/ozone/moderation
export * as ToolsOzoneModerationQueryStatuses from './types/tools/ozone/moderation/queryStatuses'
export * as ToolsOzoneModerationSearchRepos from './types/tools/ozone/moderation/searchRepos'
export * as ToolsOzoneServerGetConfig from './types/tools/ozone/server/getConfig'
export * as ToolsOzoneTeamAddMember from './types/tools/ozone/team/addMember'
export * as ToolsOzoneTeamDefs from './types/tools/ozone/team/defs'
export * as ToolsOzoneTeamDeleteMember from './types/tools/ozone/team/deleteMember'
export * as ToolsOzoneTeamListMembers from './types/tools/ozone/team/listMembers'
export * as ToolsOzoneTeamUpdateMember from './types/tools/ozone/team/updateMember'

export const COM_ATPROTO_MODERATION = {
DefsReasonSpam: 'com.atproto.moderation.defs#reasonSpam',
Expand Down Expand Up @@ -414,6 +424,11 @@ export const TOOLS_OZONE_MODERATION = {
DefsReviewClosed: 'tools.ozone.moderation.defs#reviewClosed',
DefsReviewNone: 'tools.ozone.moderation.defs#reviewNone',
}
export const TOOLS_OZONE_TEAM = {
DefsRoleAdmin: 'tools.ozone.team.defs#roleAdmin',
DefsRoleModerator: 'tools.ozone.team.defs#roleModerator',
DefsRoleTriage: 'tools.ozone.team.defs#roleTriage',
}

export class AtpBaseClient {
xrpc: XrpcClient = new XrpcClient()
Expand Down Expand Up @@ -3118,12 +3133,14 @@ export class ToolsOzoneNS {
communication: ToolsOzoneCommunicationNS
moderation: ToolsOzoneModerationNS
server: ToolsOzoneServerNS
team: ToolsOzoneTeamNS

constructor(service: AtpServiceClient) {
this._service = service
this.communication = new ToolsOzoneCommunicationNS(service)
this.moderation = new ToolsOzoneModerationNS(service)
this.server = new ToolsOzoneServerNS(service)
this.team = new ToolsOzoneTeamNS(service)
}
}

Expand Down Expand Up @@ -3282,3 +3299,55 @@ export class ToolsOzoneServerNS {
})
}
}

export class ToolsOzoneTeamNS {
_service: AtpServiceClient

constructor(service: AtpServiceClient) {
this._service = service
}

addMember(
data?: ToolsOzoneTeamAddMember.InputSchema,
opts?: ToolsOzoneTeamAddMember.CallOptions,
): Promise<ToolsOzoneTeamAddMember.Response> {
return this._service.xrpc
.call('tools.ozone.team.addMember', opts?.qp, data, opts)
.catch((e) => {
throw ToolsOzoneTeamAddMember.toKnownErr(e)
})
}

deleteMember(
data?: ToolsOzoneTeamDeleteMember.InputSchema,
opts?: ToolsOzoneTeamDeleteMember.CallOptions,
): Promise<ToolsOzoneTeamDeleteMember.Response> {
return this._service.xrpc
.call('tools.ozone.team.deleteMember', opts?.qp, data, opts)
.catch((e) => {
throw ToolsOzoneTeamDeleteMember.toKnownErr(e)
})
}

listMembers(
params?: ToolsOzoneTeamListMembers.QueryParams,
opts?: ToolsOzoneTeamListMembers.CallOptions,
): Promise<ToolsOzoneTeamListMembers.Response> {
return this._service.xrpc
.call('tools.ozone.team.listMembers', params, undefined, opts)
.catch((e) => {
throw ToolsOzoneTeamListMembers.toKnownErr(e)
})
}

updateMember(
data?: ToolsOzoneTeamUpdateMember.InputSchema,
opts?: ToolsOzoneTeamUpdateMember.CallOptions,
): Promise<ToolsOzoneTeamUpdateMember.Response> {
return this._service.xrpc
.call('tools.ozone.team.updateMember', opts?.qp, data, opts)
.catch((e) => {
throw ToolsOzoneTeamUpdateMember.toKnownErr(e)
})
}
}
Loading

0 comments on commit 3ad0519

Please sign in to comment.