Skip to content

Commit 9891286

Browse files
committed
feat(new-deepnotes): group members detail API, admin UI, and plan progress
1 parent 1c1b92a commit 9891286

21 files changed

Lines changed: 1275 additions & 21 deletions

new-deepnotes/PLAN_PROGRESS.md

Lines changed: 24 additions & 8 deletions
Large diffs are not rendered by default.

new-deepnotes/apps/api-worker/src/index.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ describe("api-worker", () => {
5050
"GET",
5151
"/api/groups/aaaaaaaaaaaaaaaaaaaaa/members",
5252
],
53+
[
54+
"GET",
55+
"/api/groups/aaaaaaaaaaaaaaaaaaaaa/members/detail",
56+
],
5357
[
5458
"GET",
5559
"/api/groups/aaaaaaaaaaaaaaaaaaaaa/pages",

new-deepnotes/apps/api-worker/src/index.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,47 @@ app.get("/api/groups/:groupId/members", async (c) => {
12871287
}
12881288
});
12891289

1290+
app.get("/api/groups/:groupId/members/detail", async (c) => {
1291+
const sessionEnv = getSessionEnv(c.env);
1292+
if (sessionEnv == null) {
1293+
return c.json(serviceUnavailableBody, 503);
1294+
}
1295+
const hyper = c.env.HYPERDRIVE;
1296+
if (hyper == null) {
1297+
return c.json(
1298+
{
1299+
code: "SERVICE_UNAVAILABLE" as const,
1300+
message: "HYPERDRIVE binding is not configured.",
1301+
},
1302+
503,
1303+
);
1304+
}
1305+
1306+
const db = getDbForConnectionString(hyper.connectionString);
1307+
const cookieHeader = c.req.header("Cookie");
1308+
const groupId = c.req.param("groupId");
1309+
1310+
try {
1311+
const { performGetGroupMembersDetail } = await import("@deepnotes/session");
1312+
const out = await performGetGroupMembersDetail({
1313+
db,
1314+
env: sessionEnv,
1315+
accessCookie: readCookieHeader(cookieHeader, "accessToken"),
1316+
groupId,
1317+
});
1318+
return c.json(out, 200);
1319+
} catch (e) {
1320+
const { SessionError } = await import("@deepnotes/session");
1321+
if (e instanceof SessionError) {
1322+
return c.json(
1323+
{ code: e.code, message: e.message },
1324+
e.status as ContentfulStatusCode,
1325+
);
1326+
}
1327+
throw e;
1328+
}
1329+
});
1330+
12901331
app.get("/api/groups/:groupId/pages", async (c) => {
12911332
const sessionEnv = getSessionEnv(c.env);
12921333
if (sessionEnv == null) {

new-deepnotes/apps/web/src/api/api-types.generated.ts

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,6 +1279,83 @@ export interface paths {
12791279
patch?: never;
12801280
trace?: never;
12811281
};
1282+
"/api/groups/{groupId}/members/detail": {
1283+
parameters: {
1284+
query?: never;
1285+
header?: never;
1286+
path?: never;
1287+
cookie?: never;
1288+
};
1289+
/**
1290+
* Group membership detail (roles + pending invites/requests)
1291+
* @description Returns the caller’s role, full member list with roles, pending invitations and non-rejected join requests, and group flags (`groupIsPublic`, `joinRequestsAllowed`). Requires `viewGroupMembers` and an active membership row.
1292+
*/
1293+
get: {
1294+
parameters: {
1295+
query?: never;
1296+
header?: never;
1297+
path: {
1298+
groupId: string;
1299+
};
1300+
cookie?: never;
1301+
};
1302+
requestBody?: never;
1303+
responses: {
1304+
/** @description Structured membership for admin UIs. */
1305+
200: {
1306+
headers: {
1307+
[name: string]: unknown;
1308+
};
1309+
content: {
1310+
"application/json": components["schemas"]["GroupMembersDetailResponse"];
1311+
};
1312+
};
1313+
/** @description Invalid credentials, token, or session state. */
1314+
401: {
1315+
headers: {
1316+
[name: string]: unknown;
1317+
};
1318+
content: {
1319+
"application/json": components["schemas"]["SessionErrorResponse"];
1320+
};
1321+
};
1322+
/** @description Action not allowed for this account (e.g. demo user). */
1323+
403: {
1324+
headers: {
1325+
[name: string]: unknown;
1326+
};
1327+
content: {
1328+
"application/json": components["schemas"]["SessionErrorResponse"];
1329+
};
1330+
};
1331+
/** @description Resource not found. */
1332+
404: {
1333+
headers: {
1334+
[name: string]: unknown;
1335+
};
1336+
content: {
1337+
"application/json": components["schemas"]["SessionErrorResponse"];
1338+
};
1339+
};
1340+
/** @description Required auth environment variables are not configured (local: copy template.env / .dev.vars). */
1341+
503: {
1342+
headers: {
1343+
[name: string]: unknown;
1344+
};
1345+
content: {
1346+
"application/json": components["schemas"]["ServiceUnavailableResponse"];
1347+
};
1348+
};
1349+
};
1350+
};
1351+
put?: never;
1352+
post?: never;
1353+
delete?: never;
1354+
options?: never;
1355+
head?: never;
1356+
patch?: never;
1357+
trace?: never;
1358+
};
12821359
"/api/groups/{groupId}/pages": {
12831360
parameters: {
12841361
query?: never;
@@ -5502,6 +5579,28 @@ export interface components {
55025579
GroupMemberUserIdsResponse: {
55035580
userIds: string[];
55045581
};
5582+
/** @enum {string} */
5583+
GroupMemberRole: "owner" | "admin" | "moderator" | "member" | "viewer";
5584+
GroupMemberRow: {
5585+
userId: string;
5586+
role: components["schemas"]["GroupMemberRole"];
5587+
};
5588+
GroupPendingInvitationRow: {
5589+
userId: string;
5590+
role: components["schemas"]["GroupMemberRole"];
5591+
};
5592+
GroupPendingJoinRequestRow: {
5593+
userId: string;
5594+
};
5595+
GroupMembersDetailResponse: {
5596+
viewerUserId: string;
5597+
viewerRole: components["schemas"]["GroupMemberRole"];
5598+
groupIsPublic: boolean;
5599+
joinRequestsAllowed: boolean;
5600+
members: components["schemas"]["GroupMemberRow"][];
5601+
pendingInvitations: components["schemas"]["GroupPendingInvitationRow"][];
5602+
pendingJoinRequests: components["schemas"]["GroupPendingJoinRequestRow"][];
5603+
};
55055604
GroupPagesListResponse: {
55065605
pageIds: string[];
55075606
hasMore: boolean;
@@ -5719,8 +5818,6 @@ export interface components {
57195818
[key: string]: components["schemas"]["GroupPrivacyPrivatePage"];
57205819
};
57215820
};
5722-
/** @enum {string} */
5723-
GroupMemberRole: "owner" | "admin" | "moderator" | "member" | "viewer";
57245821
GroupJoinInvitationSendRequest: {
57255822
/**
57265823
* @description 21-character nanoid (URL-safe alphabet).

new-deepnotes/apps/web/src/api/openapi.json

Lines changed: 166 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,101 @@
551551
"userIds"
552552
]
553553
},
554+
"GroupMemberRole": {
555+
"type": "string",
556+
"enum": [
557+
"owner",
558+
"admin",
559+
"moderator",
560+
"member",
561+
"viewer"
562+
]
563+
},
564+
"GroupMemberRow": {
565+
"type": "object",
566+
"properties": {
567+
"userId": {
568+
"type": "string"
569+
},
570+
"role": {
571+
"$ref": "#/components/schemas/GroupMemberRole"
572+
}
573+
},
574+
"required": [
575+
"userId",
576+
"role"
577+
]
578+
},
579+
"GroupPendingInvitationRow": {
580+
"type": "object",
581+
"properties": {
582+
"userId": {
583+
"type": "string"
584+
},
585+
"role": {
586+
"$ref": "#/components/schemas/GroupMemberRole"
587+
}
588+
},
589+
"required": [
590+
"userId",
591+
"role"
592+
]
593+
},
594+
"GroupPendingJoinRequestRow": {
595+
"type": "object",
596+
"properties": {
597+
"userId": {
598+
"type": "string"
599+
}
600+
},
601+
"required": [
602+
"userId"
603+
]
604+
},
605+
"GroupMembersDetailResponse": {
606+
"type": "object",
607+
"properties": {
608+
"viewerUserId": {
609+
"type": "string"
610+
},
611+
"viewerRole": {
612+
"$ref": "#/components/schemas/GroupMemberRole"
613+
},
614+
"groupIsPublic": {
615+
"type": "boolean"
616+
},
617+
"joinRequestsAllowed": {
618+
"type": "boolean"
619+
},
620+
"members": {
621+
"type": "array",
622+
"items": {
623+
"$ref": "#/components/schemas/GroupMemberRow"
624+
}
625+
},
626+
"pendingInvitations": {
627+
"type": "array",
628+
"items": {
629+
"$ref": "#/components/schemas/GroupPendingInvitationRow"
630+
}
631+
},
632+
"pendingJoinRequests": {
633+
"type": "array",
634+
"items": {
635+
"$ref": "#/components/schemas/GroupPendingJoinRequestRow"
636+
}
637+
}
638+
},
639+
"required": [
640+
"viewerUserId",
641+
"viewerRole",
642+
"groupIsPublic",
643+
"joinRequestsAllowed",
644+
"members",
645+
"pendingInvitations",
646+
"pendingJoinRequests"
647+
]
648+
},
554649
"GroupPagesListResponse": {
555650
"type": "object",
556651
"properties": {
@@ -941,16 +1036,6 @@
9411036
"groupPages"
9421037
]
9431038
},
944-
"GroupMemberRole": {
945-
"type": "string",
946-
"enum": [
947-
"owner",
948-
"admin",
949-
"moderator",
950-
"member",
951-
"viewer"
952-
]
953-
},
9541039
"GroupJoinInvitationSendRequest": {
9551040
"type": "object",
9561041
"properties": {
@@ -2598,6 +2683,77 @@
25982683
}
25992684
}
26002685
},
2686+
"/api/groups/{groupId}/members/detail": {
2687+
"get": {
2688+
"summary": "Group membership detail (roles + pending invites/requests)",
2689+
"description": "Returns the caller’s role, full member list with roles, pending invitations and non-rejected join requests, and group flags (`groupIsPublic`, `joinRequestsAllowed`). Requires `viewGroupMembers` and an active membership row.",
2690+
"parameters": [
2691+
{
2692+
"schema": {
2693+
"type": "string",
2694+
"pattern": "^[A-Za-z0-9_-]{21}$",
2695+
"description": "21-character nanoid (URL-safe alphabet).",
2696+
"example": "V1StGXR8_Z5jdHi6B-myT"
2697+
},
2698+
"required": true,
2699+
"name": "groupId",
2700+
"in": "path"
2701+
}
2702+
],
2703+
"responses": {
2704+
"200": {
2705+
"description": "Structured membership for admin UIs.",
2706+
"content": {
2707+
"application/json": {
2708+
"schema": {
2709+
"$ref": "#/components/schemas/GroupMembersDetailResponse"
2710+
}
2711+
}
2712+
}
2713+
},
2714+
"401": {
2715+
"description": "Invalid credentials, token, or session state.",
2716+
"content": {
2717+
"application/json": {
2718+
"schema": {
2719+
"$ref": "#/components/schemas/SessionErrorResponse"
2720+
}
2721+
}
2722+
}
2723+
},
2724+
"403": {
2725+
"description": "Action not allowed for this account (e.g. demo user).",
2726+
"content": {
2727+
"application/json": {
2728+
"schema": {
2729+
"$ref": "#/components/schemas/SessionErrorResponse"
2730+
}
2731+
}
2732+
}
2733+
},
2734+
"404": {
2735+
"description": "Resource not found.",
2736+
"content": {
2737+
"application/json": {
2738+
"schema": {
2739+
"$ref": "#/components/schemas/SessionErrorResponse"
2740+
}
2741+
}
2742+
}
2743+
},
2744+
"503": {
2745+
"description": "Required auth environment variables are not configured (local: copy template.env / .dev.vars).",
2746+
"content": {
2747+
"application/json": {
2748+
"schema": {
2749+
"$ref": "#/components/schemas/ServiceUnavailableResponse"
2750+
}
2751+
}
2752+
}
2753+
}
2754+
}
2755+
}
2756+
},
26012757
"/api/groups/{groupId}/pages": {
26022758
"get": {
26032759
"summary": "List page IDs in a group",

0 commit comments

Comments
 (0)