Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions apps/website/app/utils/supabase/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,30 @@ export const createGroup = async (
);
return result.data?.group_id || null;
};

export const removeFromGroup = async ({
client,
groupId,
memberId,
}: {
client: DGSupabaseClient;
groupId: string;
memberId?: string;
}): Promise<string | null> => {
if (memberId === undefined) {
const userData = await getSessionBaseUserData(client);
memberId = userData?.id ?? undefined;
if (memberId === undefined) return "Not logged in";
}
const response = await client
.from("group_membership")
.delete()
.eq("member_id", memberId)
.eq("group_id", groupId)
.select();
if (response.error) return response.error.message;
if (response.data === null || response.data.length === 0)
return "No such record";

return null; // success
Comment thread
maparent marked this conversation as resolved.
};
135 changes: 135 additions & 0 deletions apps/website/test/integration/leaveGroup.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import assert from "assert";
import { describe, it, beforeAll, afterAll } from "vitest";
import { createClient } from "@supabase/supabase-js";
import type { Database } from "@repo/database/dbTypes";
import type { DGSupabaseClient } from "@repo/database/lib/client";
import {
fetchOrCreateSpaceDirect,
spaceAnonUserEmail,
} from "@repo/database/lib/contextFunctions";
import { createGroup, removeFromGroup } from "../../app/utils/supabase/account";

const SUPABASE_URL = process.env.SUPABASE_URL!;
const ANON_KEY = process.env.SUPABASE_PUBLISHABLE_KEY!;
const SERVICE_KEY = process.env.SUPABASE_SECRET_KEY!;
const PASSWORD = "abcdefgh";

const freshClient = (): DGSupabaseClient =>
createClient<Database, "public">(SUPABASE_URL, ANON_KEY);

const serviceClient = () =>
createClient<Database, "public">(SUPABASE_URL, SERVICE_KEY);

const signedInClient = async (spaceId: number): Promise<DGSupabaseClient> => {
const client = freshClient();
const { error } = await client.auth.signInWithPassword({
email: spaceAnonUserEmail("Roam", spaceId),
password: PASSWORD,
});
if (error) throw new Error(`Sign-in failed: ${error.message}`);
return client;
};

describe("leave group flow", { tags: ["database"] }, () => {
let spaceId1: number;
let spaceId2: number;
let spaceAccountUuid1: string;
let spaceAccountUuid2: string;
let client1: DGSupabaseClient;
let client2: DGSupabaseClient;
let createdGroupId: string | null = null;

beforeAll(async () => {
const s1 = await fetchOrCreateSpaceDirect({
name: "vitest-s1",
url: "https://roamresearch.com/#/app/vitest-s1",
platform: "Roam",
password: PASSWORD,
});
if (!s1.data)
throw new Error(`Failed to create space 1: ${s1.error?.message}`);
spaceId1 = s1.data.id;
client1 = await signedInClient(spaceId1);
assert(client1);
const accountReq1 = await client1
.from("PlatformAccount")
.select("id,dg_account")
.eq(
"account_local_id",
`roam-${spaceId1}-anon@database.discoursegraphs.com`,
)
.maybeSingle();
assert(!accountReq1.error);
assert(accountReq1.data);
assert(accountReq1.data.dg_account);
spaceAccountUuid1 = accountReq1.data.dg_account;
const s2 = await fetchOrCreateSpaceDirect({
name: "vitest-s2",
url: "https://roamresearch.com/#/app/vitest-s2",
platform: "Roam",
password: PASSWORD,
});
if (!s2.data)
throw new Error(`Failed to create space 2: ${s2.error?.message}`);
spaceId2 = s2.data.id;
client2 = await signedInClient(spaceId2);
assert(client2);
const accountReq2 = await client2
.from("PlatformAccount")
.select("id,dg_account")
.eq(
"account_local_id",
`roam-${spaceId2}-anon@database.discoursegraphs.com`,
)
.maybeSingle();
assert(!accountReq2.error);
assert(accountReq2.data);
assert(accountReq2.data.dg_account);
spaceAccountUuid2 = accountReq2.data.dg_account;
});

afterAll(async () => {
if (createdGroupId)
await serviceClient().auth.admin.deleteUser(createdGroupId);
if (spaceAccountUuid1)
await serviceClient().auth.admin.deleteUser(spaceAccountUuid1);
if (spaceAccountUuid2)
await serviceClient().auth.admin.deleteUser(spaceAccountUuid2);
if (spaceId1)
await serviceClient().from("Space").delete().eq("id", spaceId1);
if (spaceId2)
await serviceClient().from("Space").delete().eq("id", spaceId2);
});

it("lists group members", async () => {
// Step 1: user1 creates a group
const groupId = await createGroup(client1, "vitest-invite-group");
assert(groupId !== null, "createGroup should return a group ID");
createdGroupId = groupId;

// Step 2: Add another member
const { error: errorAddMember } = await client1
.from("group_membership")
.insert({
member_id: spaceAccountUuid2, // eslint-disable-line @typescript-eslint/naming-convention
group_id: groupId, // eslint-disable-line @typescript-eslint/naming-convention
admin: false,
});
assert(!errorAddMember);

// Client 2 should now be part of the group.
const groupResponseBefore = await client2.from("my_groups").select();
assert(!groupResponseBefore.error);
assert(groupResponseBefore.data !== null);
assert(groupResponseBefore.data.length === 1);
assert(groupResponseBefore.data[0]!.id === groupId);
// remove self from group
const removeResult = await removeFromGroup({ client: client2, groupId });
assert(removeResult === null, removeResult!);
// Client 2 should now not be part of the group.
const groupResponseAfter = await client2.from("my_groups").select();
assert(!groupResponseAfter.error);
assert(groupResponseAfter.data !== null);
assert(groupResponseAfter.data.length === 0);
});
});
Loading