Skip to content

Commit 6f88198

Browse files
committed
fix(sds): align organization and collaborator operations with SDS API contracts
- Add creatorDid parameter to organization.create endpoint - Change organization.list response parsing from repositories to organizations field - Update accessType values from owner|collaborator to owner|shared|none - Add permission string array parser for collaborator.list endpoint - Update type definitions to match actual SDS API response formats
1 parent 8c0b6e6 commit 6f88198

File tree

9 files changed

+87
-43
lines changed

9 files changed

+87
-43
lines changed

packages/lexicon/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@hypercerts-org/lexicon",
3-
"version": "0.3.0",
3+
"version": "0.4.0",
44
"description": "ATProto lexicon definitions and TypeScript types for the Hypercerts protocol",
55
"type": "module",
66
"main": "dist/index.cjs",

packages/sdk-core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@hypercerts-org/sdk-core",
3-
"version": "0.3.0",
3+
"version": "0.4.0",
44
"description": "Framework-agnostic ATProto SDK core for authentication, repository operations, and lexicon management",
55
"main": "dist/index.cjs",
66
"repository": {

packages/sdk-core/src/core/types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,10 @@ export const OrganizationSchema = z.object({
185185
/**
186186
* How the current user relates to this organization.
187187
* - `"owner"`: User created or owns the organization
188-
* - `"collaborator"`: User was invited to collaborate
188+
* - `"shared"`: User was invited to collaborate (has permissions)
189+
* - `"none"`: User has no access to this organization
189190
*/
190-
accessType: z.enum(["owner", "collaborator"]),
191+
accessType: z.enum(["owner", "shared", "none"]),
191192
});
192193

193194
/**

packages/sdk-core/src/repository/CollaboratorOperationsImpl.ts

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,27 @@ export class CollaboratorOperationsImpl implements CollaboratorOperations {
107107
return "viewer";
108108
}
109109

110+
/**
111+
* Converts a permission string array to a permissions object.
112+
*
113+
* The SDS API returns permissions as an array of strings (e.g., ["read", "create"]).
114+
* This method converts them to the boolean flag format used by the SDK.
115+
*
116+
* @param permissionArray - Array of permission strings from SDS API
117+
* @returns Permission flags object
118+
* @internal
119+
*/
120+
private parsePermissions(permissionArray: string[]): CollaboratorPermissions {
121+
return {
122+
read: permissionArray.includes("read"),
123+
create: permissionArray.includes("create"),
124+
update: permissionArray.includes("update"),
125+
delete: permissionArray.includes("delete"),
126+
admin: permissionArray.includes("admin"),
127+
owner: permissionArray.includes("owner"),
128+
};
129+
}
130+
110131
/**
111132
* Grants repository access to a user.
112133
*
@@ -225,18 +246,21 @@ export class CollaboratorOperationsImpl implements CollaboratorOperations {
225246
return (data.collaborators || []).map(
226247
(c: {
227248
userDid: string;
228-
permissions: CollaboratorPermissions;
249+
permissions: string[]; // SDS API returns string array
229250
grantedBy: string;
230251
grantedAt: string;
231252
revokedAt?: string;
232-
}) => ({
233-
userDid: c.userDid,
234-
role: this.permissionsToRole(c.permissions),
235-
permissions: c.permissions,
236-
grantedBy: c.grantedBy,
237-
grantedAt: c.grantedAt,
238-
revokedAt: c.revokedAt,
239-
}),
253+
}) => {
254+
const permissions = this.parsePermissions(c.permissions);
255+
return {
256+
userDid: c.userDid,
257+
role: this.permissionsToRole(permissions),
258+
permissions: permissions,
259+
grantedBy: c.grantedBy,
260+
grantedAt: c.grantedAt,
261+
revokedAt: c.revokedAt,
262+
};
263+
},
240264
);
241265
}
242266

packages/sdk-core/src/repository/OrganizationOperationsImpl.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,18 @@ export class OrganizationOperationsImpl implements OrganizationOperations {
109109
* ```
110110
*/
111111
async create(params: { name: string; description?: string; handle?: string }): Promise<OrganizationInfo> {
112+
const userDid = this.session.did || this.session.sub;
113+
if (!userDid) {
114+
throw new NetworkError("No authenticated user found");
115+
}
116+
112117
const response = await this.session.fetchHandler(`${this.serverUrl}/xrpc/com.sds.organization.create`, {
113118
method: "POST",
114119
headers: { "Content-Type": "application/json" },
115-
body: JSON.stringify(params),
120+
body: JSON.stringify({
121+
...params,
122+
creatorDid: userDid,
123+
}),
116124
});
117125

118126
if (!response.ok) {
@@ -126,8 +134,15 @@ export class OrganizationOperationsImpl implements OrganizationOperations {
126134
name: data.name,
127135
description: data.description,
128136
createdAt: data.createdAt || new Date().toISOString(),
129-
accessType: "owner",
130-
permissions: { read: true, create: true, update: true, delete: true, admin: true, owner: true },
137+
accessType: data.accessType || "owner",
138+
permissions: data.permissions || {
139+
read: true,
140+
create: true,
141+
update: true,
142+
delete: true,
143+
admin: true,
144+
owner: true,
145+
},
131146
};
132147
}
133148

@@ -202,8 +217,13 @@ export class OrganizationOperationsImpl implements OrganizationOperations {
202217
* ```
203218
*/
204219
async list(): Promise<OrganizationInfo[]> {
220+
const userDid = this.session.did || this.session.sub;
221+
if (!userDid) {
222+
throw new NetworkError("No authenticated user found");
223+
}
224+
205225
const response = await this.session.fetchHandler(
206-
`${this.serverUrl}/xrpc/com.sds.organization.list?userDid=${encodeURIComponent(this.session.did || this.session.sub)}`,
226+
`${this.serverUrl}/xrpc/com.sds.organization.list?userDid=${encodeURIComponent(userDid)}`,
207227
{ method: "GET" },
208228
);
209229

@@ -212,20 +232,21 @@ export class OrganizationOperationsImpl implements OrganizationOperations {
212232
}
213233

214234
const data = await response.json();
215-
return (data.repositories || []).map(
235+
return (data.organizations || []).map(
216236
(r: {
217237
did: string;
218238
handle: string;
219239
name: string;
220240
description?: string;
221-
accessType: "owner" | "collaborator";
241+
createdAt?: string;
242+
accessType: "owner" | "shared" | "none";
222243
permissions: CollaboratorPermissions;
223244
}) => ({
224245
did: r.did,
225246
handle: r.handle,
226247
name: r.name,
227248
description: r.description,
228-
createdAt: new Date().toISOString(), // SDS may not return this
249+
createdAt: r.createdAt || new Date().toISOString(),
229250
accessType: r.accessType,
230251
permissions: r.permissions,
231252
}),

packages/sdk-core/src/repository/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export interface OrganizationInfo {
8585
name: string;
8686
description?: string;
8787
createdAt: string;
88-
accessType: "owner" | "collaborator";
88+
accessType: "owner" | "shared" | "none";
8989
permissions: CollaboratorPermissions;
9090
collaboratorCount?: number;
9191
profile?: {

packages/sdk-core/tests/repository/CollaboratorOperationsImpl.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,13 @@ describe("CollaboratorOperationsImpl", () => {
131131
collaborators: [
132132
{
133133
userDid: "did:plc:user1",
134-
permissions: { read: true, create: true, update: true, delete: false, admin: false, owner: false },
134+
permissions: ["read", "create", "update"],
135135
grantedBy: "did:plc:owner",
136136
grantedAt: "2024-01-01T00:00:00Z",
137137
},
138138
{
139139
userDid: "did:plc:user2",
140-
permissions: { read: true, create: false, update: false, delete: false, admin: false, owner: false },
140+
permissions: ["read"],
141141
grantedBy: "did:plc:owner",
142142
grantedAt: "2024-01-02T00:00:00Z",
143143
},
@@ -171,13 +171,13 @@ describe("CollaboratorOperationsImpl", () => {
171171
collaborators: [
172172
{
173173
userDid: "did:plc:owner",
174-
permissions: { read: true, create: true, update: true, delete: true, admin: true, owner: true },
174+
permissions: ["read", "create", "update", "delete", "admin", "owner"],
175175
grantedBy: "did:plc:system",
176176
grantedAt: "2024-01-01T00:00:00Z",
177177
},
178178
{
179179
userDid: "did:plc:admin",
180-
permissions: { read: true, create: true, update: true, delete: true, admin: true, owner: false },
180+
permissions: ["read", "create", "update", "delete", "admin"],
181181
grantedBy: "did:plc:owner",
182182
grantedAt: "2024-01-01T00:00:00Z",
183183
},
@@ -209,7 +209,7 @@ describe("CollaboratorOperationsImpl", () => {
209209
collaborators: [
210210
{
211211
userDid: "did:plc:activeuser",
212-
permissions: { read: true, create: false, update: false, delete: false, admin: false, owner: false },
212+
permissions: ["read"],
213213
grantedBy: "did:plc:owner",
214214
grantedAt: "2024-01-01T00:00:00Z",
215215
},
@@ -240,7 +240,7 @@ describe("CollaboratorOperationsImpl", () => {
240240
collaborators: [
241241
{
242242
userDid: "did:plc:revokeduser",
243-
permissions: { read: true, create: false, update: false, delete: false, admin: false, owner: false },
243+
permissions: ["read"],
244244
grantedBy: "did:plc:owner",
245245
grantedAt: "2024-01-01T00:00:00Z",
246246
revokedAt: "2024-02-01T00:00:00Z",
@@ -271,7 +271,7 @@ describe("CollaboratorOperationsImpl", () => {
271271
collaborators: [
272272
{
273273
userDid: "did:plc:editor",
274-
permissions: { read: true, create: true, update: true, delete: false, admin: false, owner: false },
274+
permissions: ["read", "create", "update"],
275275
grantedBy: "did:plc:owner",
276276
grantedAt: "2024-01-01T00:00:00Z",
277277
},
@@ -302,7 +302,7 @@ describe("CollaboratorOperationsImpl", () => {
302302
collaborators: [
303303
{
304304
userDid: "did:plc:revoked",
305-
permissions: { read: true, create: true, update: true, delete: false, admin: false, owner: false },
305+
permissions: ["read", "create", "update"],
306306
grantedBy: "did:plc:owner",
307307
grantedAt: "2024-01-01T00:00:00Z",
308308
revokedAt: "2024-02-01T00:00:00Z",

packages/sdk-core/tests/repository/OrganizationOperationsImpl.test.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,7 @@ describe("OrganizationOperationsImpl", () => {
7474
statusText: "Conflict",
7575
});
7676

77-
await expect(
78-
orgOps.create({ name: "Test Org" }),
79-
).rejects.toThrow(NetworkError);
77+
await expect(orgOps.create({ name: "Test Org" })).rejects.toThrow(NetworkError);
8078
});
8179
});
8280

@@ -85,7 +83,7 @@ describe("OrganizationOperationsImpl", () => {
8583
mockSession.fetchHandler.mockResolvedValue({
8684
ok: true,
8785
json: async () => ({
88-
repositories: [
86+
organizations: [
8987
{
9088
did: "did:plc:org1",
9189
handle: "org1.example.com",
@@ -98,7 +96,7 @@ describe("OrganizationOperationsImpl", () => {
9896
did: "did:plc:org2",
9997
handle: "org2.example.com",
10098
name: "Organization 2",
101-
accessType: "collaborator",
99+
accessType: "shared",
102100
permissions: { read: true, create: true, update: true, delete: false, admin: false, owner: false },
103101
},
104102
],
@@ -116,7 +114,7 @@ describe("OrganizationOperationsImpl", () => {
116114
mockSession.fetchHandler.mockResolvedValue({
117115
ok: true,
118116
json: async () => ({
119-
repositories: [],
117+
organizations: [],
120118
}),
121119
});
122120

@@ -139,7 +137,7 @@ describe("OrganizationOperationsImpl", () => {
139137
mockSession.fetchHandler.mockResolvedValue({
140138
ok: true,
141139
json: async () => ({
142-
repositories: [
140+
organizations: [
143141
{
144142
did: "did:plc:org1",
145143
handle: "org1.example.com",
@@ -152,7 +150,7 @@ describe("OrganizationOperationsImpl", () => {
152150
handle: "org2.example.com",
153151
name: "Organization 2",
154152
description: "Second org",
155-
accessType: "collaborator",
153+
accessType: "shared",
156154
permissions: { read: true, create: true, update: false, delete: false, admin: false, owner: false },
157155
},
158156
],
@@ -165,13 +163,13 @@ describe("OrganizationOperationsImpl", () => {
165163
expect(result[0].did).toBe("did:plc:org1");
166164
expect(result[0].accessType).toBe("owner");
167165
expect(result[1].did).toBe("did:plc:org2");
168-
expect(result[1].accessType).toBe("collaborator");
166+
expect(result[1].accessType).toBe("shared");
169167
});
170168

171169
it("should handle empty repositories list", async () => {
172170
mockSession.fetchHandler.mockResolvedValue({
173171
ok: true,
174-
json: async () => ({ repositories: [] }),
172+
json: async () => ({ organizations: [] }),
175173
});
176174

177175
const result = await orgOps.list();
@@ -182,7 +180,7 @@ describe("OrganizationOperationsImpl", () => {
182180
it("should use session DID in query", async () => {
183181
mockSession.fetchHandler.mockResolvedValue({
184182
ok: true,
185-
json: async () => ({ repositories: [] }),
183+
json: async () => ({ organizations: [] }),
186184
});
187185

188186
await orgOps.list();
@@ -197,7 +195,7 @@ describe("OrganizationOperationsImpl", () => {
197195
mockSession.did = undefined;
198196
mockSession.fetchHandler.mockResolvedValue({
199197
ok: true,
200-
json: async () => ({ repositories: [] }),
198+
json: async () => ({ organizations: [] }),
201199
});
202200

203201
await orgOps.list();
@@ -221,7 +219,7 @@ describe("OrganizationOperationsImpl", () => {
221219
mockSession.fetchHandler.mockResolvedValue({
222220
ok: true,
223221
json: async () => ({
224-
repositories: [
222+
organizations: [
225223
{
226224
did: "did:plc:org",
227225
handle: "org.example.com",

packages/sdk-react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@hypercerts-org/sdk-react",
3-
"version": "0.3.0",
3+
"version": "0.4.0",
44
"description": "React hooks and components for the Hypercerts ATProto SDK",
55
"type": "module",
66
"main": "dist/index.cjs",

0 commit comments

Comments
 (0)