@atproto/ozone 0.1.25 → 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 (111) hide show
  1. package/CHANGELOG.md +9 -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/server/getConfig.d.ts.map +1 -1
  6. package/dist/api/server/getConfig.js +4 -3
  7. package/dist/api/server/getConfig.js.map +1 -1
  8. package/dist/api/team/addMember.d.ts +4 -0
  9. package/dist/api/team/addMember.d.ts.map +1 -0
  10. package/dist/api/team/addMember.js +38 -0
  11. package/dist/api/team/addMember.js.map +1 -0
  12. package/dist/api/team/deleteMember.d.ts +4 -0
  13. package/dist/api/team/deleteMember.d.ts.map +1 -0
  14. package/dist/api/team/deleteMember.js +26 -0
  15. package/dist/api/team/deleteMember.js.map +1 -0
  16. package/dist/api/team/listMembers.d.ts +4 -0
  17. package/dist/api/team/listMembers.d.ts.map +1 -0
  18. package/dist/api/team/listMembers.js +20 -0
  19. package/dist/api/team/listMembers.js.map +1 -0
  20. package/dist/api/team/updateMember.d.ts +4 -0
  21. package/dist/api/team/updateMember.d.ts.map +1 -0
  22. package/dist/api/team/updateMember.js +40 -0
  23. package/dist/api/team/updateMember.js.map +1 -0
  24. package/dist/api/util.d.ts +1 -0
  25. package/dist/api/util.d.ts.map +1 -1
  26. package/dist/api/util.js +10 -1
  27. package/dist/api/util.js.map +1 -1
  28. package/dist/auth-verifier.d.ts +3 -6
  29. package/dist/auth-verifier.d.ts.map +1 -1
  30. package/dist/auth-verifier.js +7 -19
  31. package/dist/auth-verifier.js.map +1 -1
  32. package/dist/context.d.ts +3 -0
  33. package/dist/context.d.ts.map +1 -1
  34. package/dist/context.js +7 -3
  35. package/dist/context.js.map +1 -1
  36. package/dist/db/migrations/20240521T211332580Z-member.d.ts +4 -0
  37. package/dist/db/migrations/20240521T211332580Z-member.d.ts.map +1 -0
  38. package/dist/db/migrations/20240521T211332580Z-member.js +20 -0
  39. package/dist/db/migrations/20240521T211332580Z-member.js.map +1 -0
  40. package/dist/db/migrations/index.d.ts +1 -0
  41. package/dist/db/migrations/index.d.ts.map +1 -1
  42. package/dist/db/migrations/index.js +2 -1
  43. package/dist/db/migrations/index.js.map +1 -1
  44. package/dist/db/schema/index.d.ts +2 -1
  45. package/dist/db/schema/index.d.ts.map +1 -1
  46. package/dist/db/schema/member.d.ts +14 -0
  47. package/dist/db/schema/member.d.ts.map +1 -0
  48. package/dist/db/schema/member.js +5 -0
  49. package/dist/db/schema/member.js.map +1 -0
  50. package/dist/index.d.ts +1 -0
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +25 -0
  53. package/dist/index.js.map +1 -1
  54. package/dist/lexicon/index.d.ts +18 -0
  55. package/dist/lexicon/index.d.ts.map +1 -1
  56. package/dist/lexicon/index.js +41 -1
  57. package/dist/lexicon/index.js.map +1 -1
  58. package/dist/lexicon/lexicons.d.ts +204 -0
  59. package/dist/lexicon/lexicons.d.ts.map +1 -1
  60. package/dist/lexicon/lexicons.js +222 -0
  61. package/dist/lexicon/lexicons.js.map +1 -1
  62. package/dist/lexicon/types/tools/ozone/team/addMember.d.ts +40 -0
  63. package/dist/lexicon/types/tools/ozone/team/addMember.d.ts.map +1 -0
  64. package/dist/lexicon/types/tools/ozone/team/addMember.js +3 -0
  65. package/dist/lexicon/types/tools/ozone/team/addMember.js.map +1 -0
  66. package/dist/lexicon/types/tools/ozone/team/defs.d.ts +24 -0
  67. package/dist/lexicon/types/tools/ozone/team/defs.d.ts.map +1 -0
  68. package/dist/lexicon/types/tools/ozone/team/defs.js +22 -0
  69. package/dist/lexicon/types/tools/ozone/team/defs.js.map +1 -0
  70. package/dist/lexicon/types/tools/ozone/team/deleteMember.d.ts +30 -0
  71. package/dist/lexicon/types/tools/ozone/team/deleteMember.d.ts.map +1 -0
  72. package/dist/lexicon/types/tools/ozone/team/deleteMember.js +3 -0
  73. package/dist/lexicon/types/tools/ozone/team/deleteMember.js.map +1 -0
  74. package/dist/lexicon/types/tools/ozone/team/listMembers.d.ts +38 -0
  75. package/dist/lexicon/types/tools/ozone/team/listMembers.d.ts.map +1 -0
  76. package/dist/lexicon/types/tools/ozone/team/listMembers.js +3 -0
  77. package/dist/lexicon/types/tools/ozone/team/listMembers.js.map +1 -0
  78. package/dist/lexicon/types/tools/ozone/team/updateMember.d.ts +41 -0
  79. package/dist/lexicon/types/tools/ozone/team/updateMember.d.ts.map +1 -0
  80. package/dist/lexicon/types/tools/ozone/team/updateMember.js +3 -0
  81. package/dist/lexicon/types/tools/ozone/team/updateMember.js.map +1 -0
  82. package/dist/team/index.d.ts +37 -0
  83. package/dist/team/index.d.ts.map +1 -0
  84. package/dist/team/index.js +144 -0
  85. package/dist/team/index.js.map +1 -0
  86. package/package.json +3 -3
  87. package/src/api/index.ts +8 -0
  88. package/src/api/server/getConfig.ts +4 -4
  89. package/src/api/team/addMember.ts +46 -0
  90. package/src/api/team/deleteMember.ts +29 -0
  91. package/src/api/team/listMembers.ts +20 -0
  92. package/src/api/team/updateMember.ts +47 -0
  93. package/src/api/util.ts +15 -0
  94. package/src/auth-verifier.ts +14 -12
  95. package/src/context.ts +9 -3
  96. package/src/db/migrations/20240521T211332580Z-member.ts +17 -0
  97. package/src/db/migrations/index.ts +1 -0
  98. package/src/db/schema/index.ts +3 -1
  99. package/src/db/schema/member.ts +19 -0
  100. package/src/index.ts +36 -0
  101. package/src/lexicon/index.ts +63 -0
  102. package/src/lexicon/lexicons.ts +225 -0
  103. package/src/lexicon/types/tools/ozone/team/addMember.ts +53 -0
  104. package/src/lexicon/types/tools/ozone/team/defs.ts +42 -0
  105. package/src/lexicon/types/tools/ozone/team/deleteMember.ts +39 -0
  106. package/src/lexicon/types/tools/ozone/team/listMembers.ts +48 -0
  107. package/src/lexicon/types/tools/ozone/team/updateMember.ts +54 -0
  108. package/src/team/index.ts +213 -0
  109. package/tests/__snapshots__/team.test.ts.snap +664 -0
  110. package/tests/get-config.test.ts +3 -4
  111. package/tests/team.test.ts +163 -0
@@ -0,0 +1,42 @@
1
+ /**
2
+ * GENERATED CODE - DO NOT MODIFY
3
+ */
4
+ import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+ import { lexicons } from '../../../../lexicons'
6
+ import { isObj, hasProp } from '../../../../util'
7
+ import { CID } from 'multiformats/cid'
8
+ import * as AppBskyActorDefs from '../../../app/bsky/actor/defs'
9
+
10
+ export interface Member {
11
+ did: string
12
+ disabled?: boolean
13
+ profile?: AppBskyActorDefs.ProfileViewDetailed
14
+ createdAt?: string
15
+ updatedAt?: string
16
+ lastUpdatedBy?: string
17
+ role:
18
+ | 'lex:tools.ozone.team.defs#roleAdmin'
19
+ | 'lex:tools.ozone.team.defs#roleModerator'
20
+ | 'lex:tools.ozone.team.defs#roleTriage'
21
+ | (string & {})
22
+ [k: string]: unknown
23
+ }
24
+
25
+ export function isMember(v: unknown): v is Member {
26
+ return (
27
+ isObj(v) &&
28
+ hasProp(v, '$type') &&
29
+ v.$type === 'tools.ozone.team.defs#member'
30
+ )
31
+ }
32
+
33
+ export function validateMember(v: unknown): ValidationResult {
34
+ return lexicons.validate('tools.ozone.team.defs#member', v)
35
+ }
36
+
37
+ /** Admin role. Highest level of access, can perform all actions. */
38
+ export const ROLEADMIN = 'tools.ozone.team.defs#roleAdmin'
39
+ /** Moderator role. Can perform most actions. */
40
+ export const ROLEMODERATOR = 'tools.ozone.team.defs#roleModerator'
41
+ /** Triage role. Mostly intended for monitoring and escalating issues. */
42
+ export const ROLETRIAGE = 'tools.ozone.team.defs#roleTriage'
@@ -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
+ [k: string]: unknown
16
+ }
17
+
18
+ export interface HandlerInput {
19
+ encoding: 'application/json'
20
+ body: InputSchema
21
+ }
22
+
23
+ export interface HandlerError {
24
+ status: number
25
+ message?: string
26
+ error?: 'MemberNotFound'
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
@@ -0,0 +1,48 @@
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
+ import * as ToolsOzoneTeamDefs from './defs'
11
+
12
+ export interface QueryParams {
13
+ limit: number
14
+ cursor?: string
15
+ }
16
+
17
+ export type InputSchema = undefined
18
+
19
+ export interface OutputSchema {
20
+ cursor?: string
21
+ members: ToolsOzoneTeamDefs.Member[]
22
+ [k: string]: unknown
23
+ }
24
+
25
+ export type HandlerInput = undefined
26
+
27
+ export interface HandlerSuccess {
28
+ encoding: 'application/json'
29
+ body: OutputSchema
30
+ headers?: { [key: string]: string }
31
+ }
32
+
33
+ export interface HandlerError {
34
+ status: number
35
+ message?: string
36
+ }
37
+
38
+ export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
39
+ export type HandlerReqCtx<HA extends HandlerAuth = never> = {
40
+ auth: HA
41
+ params: QueryParams
42
+ input: HandlerInput
43
+ req: express.Request
44
+ res: express.Response
45
+ }
46
+ export type Handler<HA extends HandlerAuth = never> = (
47
+ ctx: HandlerReqCtx<HA>,
48
+ ) => Promise<HandlerOutput> | HandlerOutput
@@ -0,0 +1,54 @@
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
+ import * as ToolsOzoneTeamDefs from './defs'
11
+
12
+ export interface QueryParams {}
13
+
14
+ export interface InputSchema {
15
+ did: string
16
+ disabled?: boolean
17
+ role?:
18
+ | 'tools.ozone.team.defs#roleAdmin'
19
+ | 'tools.ozone.team.defs#roleModerator'
20
+ | 'tools.ozone.team.defs#roleTriage'
21
+ | (string & {})
22
+ [k: string]: unknown
23
+ }
24
+
25
+ export type OutputSchema = ToolsOzoneTeamDefs.Member
26
+
27
+ export interface HandlerInput {
28
+ encoding: 'application/json'
29
+ body: InputSchema
30
+ }
31
+
32
+ export interface HandlerSuccess {
33
+ encoding: 'application/json'
34
+ body: OutputSchema
35
+ headers?: { [key: string]: string }
36
+ }
37
+
38
+ export interface HandlerError {
39
+ status: number
40
+ message?: string
41
+ error?: 'MemberNotFound'
42
+ }
43
+
44
+ export type HandlerOutput = HandlerError | HandlerSuccess | HandlerPipeThrough
45
+ export type HandlerReqCtx<HA extends HandlerAuth = never> = {
46
+ auth: HA
47
+ params: QueryParams
48
+ input: HandlerInput
49
+ req: express.Request
50
+ res: express.Response
51
+ }
52
+ export type Handler<HA extends HandlerAuth = never> = (
53
+ ctx: HandlerReqCtx<HA>,
54
+ ) => Promise<HandlerOutput> | HandlerOutput
@@ -0,0 +1,213 @@
1
+ import Database from '../db'
2
+ import { Selectable } from 'kysely'
3
+ import { Member } from '../db/schema/member'
4
+ import { Member as TeamMember } from '../lexicon/types/tools/ozone/team/defs'
5
+ import { ProfileViewDetailed } from '../lexicon/types/app/bsky/actor/defs'
6
+ import { InvalidRequestError } from '@atproto/xrpc-server'
7
+ import { chunkArray } from '@atproto/common'
8
+ import AppContext from '../context'
9
+ import { httpLogger } from '../logger'
10
+
11
+ export type TeamServiceCreator = (db: Database) => TeamService
12
+
13
+ export class TeamService {
14
+ constructor(public db: Database) {}
15
+
16
+ static creator() {
17
+ return (db: Database) => new TeamService(db)
18
+ }
19
+
20
+ async list({
21
+ cursor,
22
+ limit = 25,
23
+ }: {
24
+ cursor?: string
25
+ limit?: number
26
+ }): Promise<{ members: Selectable<Member>[]; cursor?: string }> {
27
+ let builder = this.db.db.selectFrom('member').selectAll()
28
+ if (cursor) {
29
+ builder = builder.where('createdAt', '>', new Date(cursor))
30
+ }
31
+ const members = await builder
32
+ .limit(limit)
33
+ .orderBy('createdAt', 'asc')
34
+ .execute()
35
+
36
+ return { members, cursor: members.at(-1)?.createdAt.toISOString() }
37
+ }
38
+
39
+ async create({
40
+ role,
41
+ did,
42
+ disabled,
43
+ updatedAt,
44
+ createdAt,
45
+ lastUpdatedBy,
46
+ }: Omit<Selectable<Member>, 'createdAt' | 'updatedAt'> & {
47
+ createdAt?: Date
48
+ updatedAt?: Date
49
+ }): Promise<Selectable<Member>> {
50
+ const now = new Date()
51
+ const newMember = await this.db.db
52
+ .insertInto('member')
53
+ .values({
54
+ role,
55
+ did,
56
+ disabled,
57
+ lastUpdatedBy,
58
+ updatedAt: updatedAt || now,
59
+ createdAt: createdAt || now,
60
+ })
61
+ .returningAll()
62
+ .executeTakeFirstOrThrow()
63
+
64
+ return newMember
65
+ }
66
+
67
+ async upsert({
68
+ role,
69
+ did,
70
+ lastUpdatedBy,
71
+ }: Pick<
72
+ Selectable<Member>,
73
+ 'role' | 'did' | 'lastUpdatedBy'
74
+ >): Promise<void> {
75
+ const now = new Date()
76
+ await this.db.db
77
+ .insertInto('member')
78
+ .values({
79
+ role,
80
+ did,
81
+ lastUpdatedBy,
82
+ disabled: false,
83
+ updatedAt: now,
84
+ createdAt: now,
85
+ })
86
+ .onConflict((oc) =>
87
+ oc.column('did').doUpdateSet({ role, updatedAt: now, lastUpdatedBy }),
88
+ )
89
+ .execute()
90
+ }
91
+
92
+ async update(
93
+ did: string,
94
+ updates: Partial<
95
+ Pick<
96
+ Selectable<Member>,
97
+ 'role' | 'disabled' | 'lastUpdatedBy' | 'updatedAt'
98
+ >
99
+ >,
100
+ ): Promise<Selectable<Member>> {
101
+ const { role, disabled, lastUpdatedBy, updatedAt = new Date() } = updates
102
+ const updatedMember = await this.db.db
103
+ .updateTable('member')
104
+ .where('did', '=', did)
105
+ .set({
106
+ role,
107
+ disabled,
108
+ lastUpdatedBy,
109
+ updatedAt,
110
+ })
111
+ .returningAll()
112
+ .executeTakeFirstOrThrow()
113
+
114
+ return updatedMember
115
+ }
116
+
117
+ async delete(did: string): Promise<void> {
118
+ await this.db.db.deleteFrom('member').where('did', '=', did).execute()
119
+ }
120
+
121
+ async assertCanDelete(did: string): Promise<void> {
122
+ const memberExists = await this.doesMemberExist(did)
123
+
124
+ if (!memberExists) {
125
+ throw new InvalidRequestError('member not found', 'MemberNotFound')
126
+ }
127
+ }
128
+
129
+ async doesMemberExist(did: string): Promise<boolean> {
130
+ const member = await this.db.db
131
+ .selectFrom('member')
132
+ .select('did')
133
+ .where('did', '=', did)
134
+ .executeTakeFirst()
135
+
136
+ return !!member
137
+ }
138
+
139
+ async getMember(did: string): Promise<Selectable<Member> | undefined> {
140
+ const member = await this.db.db
141
+ .selectFrom('member')
142
+ .selectAll()
143
+ .where('did', '=', did)
144
+ .executeTakeFirst()
145
+
146
+ return member
147
+ }
148
+
149
+ getMemberRole(member?: Selectable<Member>) {
150
+ const isAdmin = member?.role === 'tools.ozone.team.defs#roleAdmin'
151
+ const isModerator =
152
+ isAdmin || member?.role === 'tools.ozone.team.defs#roleModerator'
153
+ const isTriage =
154
+ isModerator || member?.role === 'tools.ozone.team.defs#roleTriage'
155
+
156
+ return {
157
+ isModerator,
158
+ isAdmin,
159
+ isTriage,
160
+ }
161
+ }
162
+
163
+ // getProfiles() only allows 25 DIDs at a time so we need to query in chunks
164
+ async getProfiles(
165
+ dids: string[],
166
+ ctx: AppContext,
167
+ ): Promise<Map<string, ProfileViewDetailed>> {
168
+ const profiles = new Map<string, ProfileViewDetailed>()
169
+
170
+ try {
171
+ const headers = await ctx.appviewAuth()
172
+
173
+ for (const actors of chunkArray(dids, 25)) {
174
+ const { data } = await ctx.appviewAgent.api.app.bsky.actor.getProfiles(
175
+ { actors },
176
+ headers,
177
+ )
178
+
179
+ data.profiles.forEach((profile) => {
180
+ profiles.set(profile.did, profile)
181
+ })
182
+ }
183
+ } catch (error) {
184
+ httpLogger.error(
185
+ { error, dids },
186
+ 'Failed to get profiles for team members',
187
+ )
188
+ }
189
+
190
+ return profiles
191
+ }
192
+
193
+ async view(
194
+ members: Selectable<Member>[],
195
+ ctx: AppContext,
196
+ ): Promise<TeamMember[]> {
197
+ const profiles = await this.getProfiles(
198
+ members.map(({ did }) => did),
199
+ ctx,
200
+ )
201
+ return members.map((member) => {
202
+ return {
203
+ did: member.did,
204
+ role: member.role,
205
+ disabled: member.disabled,
206
+ profile: profiles.get(member.did),
207
+ createdAt: member.createdAt.toISOString(),
208
+ updatedAt: member.updatedAt.toISOString(),
209
+ lastUpdatedBy: member.lastUpdatedBy,
210
+ }
211
+ })
212
+ }
213
+ }