Skip to content

Commit 9568d34

Browse files
authored
ENG-1724 backend function: Remove members (#1028)
1 parent 43b82a8 commit 9568d34

2 files changed

Lines changed: 162 additions & 0 deletions

File tree

apps/website/app/utils/supabase/account.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,30 @@ export const createGroup = async (
121121
);
122122
return result.data?.group_id || null;
123123
};
124+
125+
export const removeFromGroup = async ({
126+
client,
127+
groupId,
128+
memberId,
129+
}: {
130+
client: DGSupabaseClient;
131+
groupId: string;
132+
memberId?: string;
133+
}): Promise<string | null> => {
134+
if (memberId === undefined) {
135+
const userData = await getSessionBaseUserData(client);
136+
memberId = userData?.id ?? undefined;
137+
if (memberId === undefined) return "Not logged in";
138+
}
139+
const response = await client
140+
.from("group_membership")
141+
.delete()
142+
.eq("member_id", memberId)
143+
.eq("group_id", groupId)
144+
.select();
145+
if (response.error) return response.error.message;
146+
if (response.data === null || response.data.length === 0)
147+
return "No such record";
148+
149+
return null; // success
150+
};
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import assert from "assert";
2+
import { describe, it, beforeAll, afterAll } from "vitest";
3+
import { createClient } from "@supabase/supabase-js";
4+
import type { Database } from "@repo/database/dbTypes";
5+
import type { DGSupabaseClient } from "@repo/database/lib/client";
6+
import {
7+
fetchOrCreateSpaceDirect,
8+
spaceAnonUserEmail,
9+
} from "@repo/database/lib/contextFunctions";
10+
import { createGroup, removeFromGroup } from "../../app/utils/supabase/account";
11+
12+
const SUPABASE_URL = process.env.SUPABASE_URL!;
13+
const ANON_KEY = process.env.SUPABASE_PUBLISHABLE_KEY!;
14+
const SERVICE_KEY = process.env.SUPABASE_SECRET_KEY!;
15+
const PASSWORD = "abcdefgh";
16+
17+
const freshClient = (): DGSupabaseClient =>
18+
createClient<Database, "public">(SUPABASE_URL, ANON_KEY);
19+
20+
const serviceClient = () =>
21+
createClient<Database, "public">(SUPABASE_URL, SERVICE_KEY);
22+
23+
const signedInClient = async (spaceId: number): Promise<DGSupabaseClient> => {
24+
const client = freshClient();
25+
const { error } = await client.auth.signInWithPassword({
26+
email: spaceAnonUserEmail("Roam", spaceId),
27+
password: PASSWORD,
28+
});
29+
if (error) throw new Error(`Sign-in failed: ${error.message}`);
30+
return client;
31+
};
32+
33+
describe("leave group flow", { tags: ["database"] }, () => {
34+
let spaceId1: number;
35+
let spaceId2: number;
36+
let spaceAccountUuid1: string;
37+
let spaceAccountUuid2: string;
38+
let client1: DGSupabaseClient;
39+
let client2: DGSupabaseClient;
40+
let createdGroupId: string | null = null;
41+
42+
beforeAll(async () => {
43+
const s1 = await fetchOrCreateSpaceDirect({
44+
name: "vitest-s1",
45+
url: "https://roamresearch.com/#/app/vitest-s1",
46+
platform: "Roam",
47+
password: PASSWORD,
48+
});
49+
if (!s1.data)
50+
throw new Error(`Failed to create space 1: ${s1.error?.message}`);
51+
spaceId1 = s1.data.id;
52+
client1 = await signedInClient(spaceId1);
53+
assert(client1);
54+
const accountReq1 = await client1
55+
.from("PlatformAccount")
56+
.select("id,dg_account")
57+
.eq(
58+
"account_local_id",
59+
`roam-${spaceId1}-anon@database.discoursegraphs.com`,
60+
)
61+
.maybeSingle();
62+
assert(!accountReq1.error);
63+
assert(accountReq1.data);
64+
assert(accountReq1.data.dg_account);
65+
spaceAccountUuid1 = accountReq1.data.dg_account;
66+
const s2 = await fetchOrCreateSpaceDirect({
67+
name: "vitest-s2",
68+
url: "https://roamresearch.com/#/app/vitest-s2",
69+
platform: "Roam",
70+
password: PASSWORD,
71+
});
72+
if (!s2.data)
73+
throw new Error(`Failed to create space 2: ${s2.error?.message}`);
74+
spaceId2 = s2.data.id;
75+
client2 = await signedInClient(spaceId2);
76+
assert(client2);
77+
const accountReq2 = await client2
78+
.from("PlatformAccount")
79+
.select("id,dg_account")
80+
.eq(
81+
"account_local_id",
82+
`roam-${spaceId2}-anon@database.discoursegraphs.com`,
83+
)
84+
.maybeSingle();
85+
assert(!accountReq2.error);
86+
assert(accountReq2.data);
87+
assert(accountReq2.data.dg_account);
88+
spaceAccountUuid2 = accountReq2.data.dg_account;
89+
});
90+
91+
afterAll(async () => {
92+
if (createdGroupId)
93+
await serviceClient().auth.admin.deleteUser(createdGroupId);
94+
if (spaceAccountUuid1)
95+
await serviceClient().auth.admin.deleteUser(spaceAccountUuid1);
96+
if (spaceAccountUuid2)
97+
await serviceClient().auth.admin.deleteUser(spaceAccountUuid2);
98+
if (spaceId1)
99+
await serviceClient().from("Space").delete().eq("id", spaceId1);
100+
if (spaceId2)
101+
await serviceClient().from("Space").delete().eq("id", spaceId2);
102+
});
103+
104+
it("lists group members", async () => {
105+
// Step 1: user1 creates a group
106+
const groupId = await createGroup(client1, "vitest-invite-group");
107+
assert(groupId !== null, "createGroup should return a group ID");
108+
createdGroupId = groupId;
109+
110+
// Step 2: Add another member
111+
const { error: errorAddMember } = await client1
112+
.from("group_membership")
113+
.insert({
114+
member_id: spaceAccountUuid2, // eslint-disable-line @typescript-eslint/naming-convention
115+
group_id: groupId, // eslint-disable-line @typescript-eslint/naming-convention
116+
admin: false,
117+
});
118+
assert(!errorAddMember);
119+
120+
// Client 2 should now be part of the group.
121+
const groupResponseBefore = await client2.from("my_groups").select();
122+
assert(!groupResponseBefore.error);
123+
assert(groupResponseBefore.data !== null);
124+
assert(groupResponseBefore.data.length === 1);
125+
assert(groupResponseBefore.data[0]!.id === groupId);
126+
// remove self from group
127+
const removeResult = await removeFromGroup({ client: client2, groupId });
128+
assert(removeResult === null, removeResult!);
129+
// Client 2 should now not be part of the group.
130+
const groupResponseAfter = await client2.from("my_groups").select();
131+
assert(!groupResponseAfter.error);
132+
assert(groupResponseAfter.data !== null);
133+
assert(groupResponseAfter.data.length === 0);
134+
});
135+
});

0 commit comments

Comments
 (0)