Skip to content

Commit 2174d86

Browse files
authored
ENG-567 Refactor getContext so the PlatformAccount is created in a Space. (#272)
Refactor getContext so the PlatformAccount is created in a Space. Factor out some of the functions that call next from roam to ui code. Also work on a generic way to know the Next endpoint.
1 parent d25e156 commit 2174d86

8 files changed

Lines changed: 228 additions & 163 deletions

File tree

apps/roam/scripts/compile.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ export const compile = ({
132132
define: {
133133
"process.env.SUPABASE_URL": `"${process.env.SUPABASE_URL}"`,
134134
"process.env.SUPABASE_ANON_KEY": `"${process.env.SUPABASE_ANON_KEY}"`,
135+
"process.env.NEXT_API_ROOT": `"${process.env.NEXT_API_ROOT || ""}"`,
135136
},
136137
sourcemap: process.env.NODE_ENV === "production" ? undefined : "inline",
137138
minify: process.env.NODE_ENV === "production",

apps/roam/src/utils/supabaseContext.ts

Lines changed: 24 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { getNodeEnv } from "roamjs-components/util/env";
21
import getCurrentUserEmail from "roamjs-components/queries/getCurrentUserEmail";
32
import getCurrentUserDisplayName from "roamjs-components/queries/getCurrentUserDisplayName";
43
import getPageUidByPageTitle from "roamjs-components/queries/getPageUidByPageTitle";
@@ -8,11 +7,12 @@ import { Enums } from "@repo/database/types.gen";
87
import { DISCOURSE_CONFIG_PAGE_TITLE } from "~/utils/renderNodeConfigPage";
98
import getBlockProps from "~/utils/getBlockProps";
109
import setBlockProps from "~/utils/setBlockProps";
10+
import { type DGSupabaseClient } from "@repo/ui/lib/supabase/client";
1111
import {
12-
createClient,
13-
type DGSupabaseClient,
14-
} from "@repo/ui/src/lib/supabase/client";
15-
import { spaceAnonUserEmail } from "@repo/ui/lib/utils";
12+
fetchOrCreateSpaceId,
13+
fetchOrCreatePlatformAccount,
14+
createLoggedInClient,
15+
} from "@repo/ui/lib/supabase/contextFunctions";
1616

1717
declare const crypto: { randomUUID: () => string };
1818

@@ -27,12 +27,6 @@ export type SupabaseContext = {
2727

2828
let _contextCache: SupabaseContext | null = null;
2929

30-
// TODO: This should be an util on its own.
31-
const base_url =
32-
getNodeEnv() === "development"
33-
? "http://localhost:3000/api/supabase"
34-
: "https://discoursegraphs.com/api/supabase";
35-
3630
const settingsConfigPageUid = getPageUidByPageTitle(
3731
DISCOURSE_CONFIG_PAGE_TITLE,
3832
);
@@ -53,95 +47,30 @@ const getOrCreateSpacePassword = () => {
5347
// It is better if this is still at least protected by CORS.
5448
// But calls anywhere else should use the supabase client directly.
5549

56-
const fetchOrCreateSpaceId = async (
57-
account_id: number,
58-
password: string,
59-
): Promise<number> => {
60-
const url = getRoamUrl();
61-
const name = window.roamAlphaAPI.graph.name;
62-
const platform: Platform = "Roam";
63-
const response = await fetch(base_url + "/space", {
64-
method: "POST",
65-
headers: {
66-
"Content-Type": "application/json",
67-
},
68-
body: JSON.stringify({
69-
space: { url, name, platform },
70-
password,
71-
account_id,
72-
}),
73-
});
74-
if (!response.ok)
75-
throw new Error(
76-
`Platform API failed: \${response.status} \${response.statusText}`,
77-
);
78-
const space = await response.json();
79-
if (typeof space.id !== "number") throw new Error("API did not return space");
80-
return space.id;
81-
};
82-
83-
const fetchOrCreatePlatformAccount = async ({
84-
accountLocalId,
85-
personName,
86-
personEmail,
87-
}: {
88-
accountLocalId: string;
89-
personName: string;
90-
personEmail: string | undefined;
91-
}): Promise<number> => {
92-
const response = await fetch(`${base_url}/platform-account`, {
93-
method: "POST",
94-
headers: {
95-
"Content-Type": "application/json",
96-
},
97-
body: JSON.stringify({
98-
platform: "Roam",
99-
account_local_id: accountLocalId,
100-
name: personName,
101-
}),
102-
});
103-
if (!response.ok)
104-
throw new Error(
105-
`Platform API failed: \${response.status} \${response.statusText}`,
106-
);
107-
const account = await response.json();
108-
if (personEmail !== undefined) {
109-
const idResponse = await fetch(`${base_url}/agent-identifier`, {
110-
method: "POST",
111-
headers: {
112-
"Content-Type": "application/json",
113-
},
114-
body: JSON.stringify({
115-
account_id: account.id,
116-
identifier_type: "email",
117-
value: personEmail,
118-
trusted: false, // Roam tests email
119-
}),
120-
});
121-
if (!idResponse.ok) {
122-
const error = await idResponse.text();
123-
console.error(`Error setting the email for the account: ${error}`);
124-
// This is not a reason to stop here
125-
}
126-
}
127-
if (typeof account.id !== "number")
128-
throw new Error("API did not return account");
129-
return account.id;
130-
};
131-
13250
export const getSupabaseContext = async (): Promise<SupabaseContext | null> => {
13351
if (_contextCache === null) {
13452
try {
13553
const accountLocalId = window.roamAlphaAPI.user.uid();
13654
const spacePassword = getOrCreateSpacePassword();
13755
const personEmail = getCurrentUserEmail();
13856
const personName = getCurrentUserDisplayName();
57+
const url = getRoamUrl();
58+
const spaceName = window.roamAlphaAPI.graph.name;
59+
const platform: Platform = "Roam";
60+
const spaceId = await fetchOrCreateSpaceId({
61+
password: spacePassword,
62+
url,
63+
name: spaceName,
64+
platform,
65+
});
13966
const userId = await fetchOrCreatePlatformAccount({
67+
platform: "Roam",
14068
accountLocalId,
141-
personName,
142-
personEmail,
69+
name: personName,
70+
email: personEmail,
71+
spaceId,
72+
password: spacePassword,
14373
});
144-
const spaceId = await fetchOrCreateSpaceId(userId, spacePassword);
14574
_contextCache = {
14675
platform: "Roam",
14776
spaceId,
@@ -162,15 +91,11 @@ export const getLoggedInClient = async (): Promise<DGSupabaseClient> => {
16291
if (_loggedInClient === null) {
16392
const context = await getSupabaseContext();
16493
if (context === null) throw new Error("Could not create context");
165-
_loggedInClient = createClient();
166-
const { error } = await _loggedInClient.auth.signInWithPassword({
167-
email: spaceAnonUserEmail(context.platform, context.spaceId),
168-
password: context.spacePassword,
169-
});
170-
if (error) {
171-
_loggedInClient = null;
172-
throw new Error(`Authentication failed: ${error.message}`);
173-
}
94+
_loggedInClient = await createLoggedInClient(
95+
context.platform,
96+
context.spaceId,
97+
context.spacePassword,
98+
);
17499
} else {
175100
// renew session
176101
const { error } = await _loggedInClient.auth.getSession();

apps/website/app/api/supabase/space/route.ts

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,48 +18,31 @@ import { spaceAnonUserEmail } from "@repo/ui/lib/utils";
1818
type SpaceDataInput = TablesInsert<"Space">;
1919
type SpaceRecord = Tables<"Space">;
2020

21-
type SpaceCreationInput = {
22-
space: SpaceDataInput;
23-
account_id: number;
24-
password: string;
25-
};
21+
type SpaceCreationInput = SpaceDataInput & { password: string };
2622

27-
const spaceValidator: ItemValidator<SpaceDataInput> = (space) => {
23+
const spaceValidator: ItemValidator<SpaceCreationInput> = (space) => {
2824
if (!space || typeof space !== "object")
2925
return "Invalid request body: expected a JSON object.";
30-
const { name, url, platform } = space;
26+
const { name, url, platform, password } = space;
3127

3228
if (!name || typeof name !== "string" || name.trim() === "")
3329
return "Missing or invalid name.";
3430
if (!url || typeof url !== "string" || url.trim() === "")
3531
return "Missing or invalid URL.";
3632
if (platform === undefined || !["Roam", "Obsidian"].includes(platform))
3733
return "Missing or invalid platform.";
34+
if (!password || typeof password !== "string" || password.length < 8)
35+
return "password must be at least 8 characters";
3836
return null;
3937
};
4038

4139
const processAndGetOrCreateSpace = async (
4240
supabasePromise: ReturnType<typeof createClient>,
4341
data: SpaceCreationInput,
4442
): Promise<PostgrestSingleResponse<SpaceRecord>> => {
45-
const { space, account_id, password } = data;
46-
const { name, url, platform } = space;
47-
const error = spaceValidator(space);
43+
const { name, url, platform, password } = data;
44+
const error = spaceValidator(data);
4845
if (error !== null) return asPostgrestFailure(error, "invalid space");
49-
if (
50-
typeof account_id !== "number" ||
51-
!Number.isInteger(account_id) ||
52-
account_id <= 0
53-
)
54-
return asPostgrestFailure(
55-
"account_id is not a number",
56-
"invalid account_id",
57-
);
58-
if (!password || typeof password !== "string" || password.length < 8)
59-
return asPostgrestFailure(
60-
"password must be at least 8 characters",
61-
"invalid password",
62-
);
6346

6447
const supabase = await supabasePromise;
6548

@@ -141,14 +124,6 @@ const processAndGetOrCreateSpace = async (
141124
uniqueOn: ["space_id", "account_id"],
142125
});
143126
if (resultAnonUserSpaceAccess.error) return resultAnonUserSpaceAccess; // space created but not connected, try again
144-
145-
const resultUserSpaceAccess = await getOrCreateEntity<"SpaceAccess">({
146-
supabase,
147-
tableName: "SpaceAccess",
148-
insertData: { space_id, account_id, editor: true },
149-
uniqueOn: ["space_id", "account_id"],
150-
});
151-
if (resultUserSpaceAccess.error) return resultUserSpaceAccess; // space created but not connected, try again
152127
return result;
153128
};
154129

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
CREATE OR REPLACE FUNCTION public.create_account_in_space(
2+
space_id_ BIGINT,
3+
account_local_id_ varchar,
4+
name_ varchar,
5+
email_ varchar = NULL,
6+
email_trusted boolean = true,
7+
editor_ boolean = true
8+
) RETURNS BIGINT
9+
SECURITY DEFINER
10+
SET search_path = ''
11+
LANGUAGE plpgsql AS $$
12+
DECLARE
13+
platform_ public."Platform";
14+
account_id_ BIGINT;
15+
BEGIN
16+
SELECT platform INTO platform_ STRICT FROM public."Space" WHERE id = space_id_;
17+
INSERT INTO public."PlatformAccount" AS pa (
18+
account_local_id, name, platform
19+
) VALUES (
20+
account_local_id_, name_, platform_
21+
) ON CONFLICT (account_local_id, platform) DO UPDATE SET
22+
name = coalesce(name_, pa.name)
23+
RETURNING id INTO STRICT account_id_;
24+
INSERT INTO public."SpaceAccess" (space_id, account_id, editor) values (space_id_, account_id_, editor_)
25+
ON CONFLICT (space_id, account_id)
26+
DO UPDATE SET editor = editor_;
27+
IF email_ IS NOT NULL THEN
28+
INSERT INTO public."AgentIdentifier" (account_id, value, identifier_type, trusted) VALUES (account_id_, email_, 'email', email_trusted)
29+
ON CONFLICT (value, identifier_type, account_id)
30+
DO UPDATE SET trusted = email_trusted;
31+
END IF;
32+
RETURN account_id_;
33+
END;
34+
$$;

packages/database/supabase/schemas/account.sql

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,39 @@ ADD CONSTRAINT "SpaceAccess_space_id_fkey" FOREIGN KEY (
9898
GRANT ALL ON TABLE public."SpaceAccess" TO anon;
9999
GRANT ALL ON TABLE public."SpaceAccess" TO authenticated;
100100
GRANT ALL ON TABLE public."SpaceAccess" TO service_role;
101+
102+
CREATE OR REPLACE FUNCTION public.create_account_in_space(
103+
space_id_ BIGINT,
104+
account_local_id_ varchar,
105+
name_ varchar,
106+
email_ varchar = NULL,
107+
email_trusted boolean = true,
108+
editor_ boolean = true
109+
) RETURNS BIGINT
110+
SECURITY DEFINER
111+
SET search_path = ''
112+
LANGUAGE plpgsql
113+
AS $$
114+
DECLARE
115+
platform_ public."Platform";
116+
account_id_ BIGINT;
117+
BEGIN
118+
SELECT platform INTO platform_ STRICT FROM public."Space" WHERE id = space_id_;
119+
INSERT INTO public."PlatformAccount" AS pa (
120+
account_local_id, name, platform
121+
) VALUES (
122+
account_local_id_, name_, platform_
123+
) ON CONFLICT (account_local_id, platform) DO UPDATE SET
124+
name = coalesce(name_, pa.name)
125+
RETURNING id INTO STRICT account_id_;
126+
INSERT INTO public."SpaceAccess" (space_id, account_id, editor) values (space_id_, account_id_, editor_)
127+
ON CONFLICT (space_id, account_id)
128+
DO UPDATE SET editor = editor_;
129+
IF email_ IS NOT NULL THEN
130+
INSERT INTO public."AgentIdentifier" (account_id, value, identifier_type, trusted) VALUES (account_id_, email_, 'email', email_trusted)
131+
ON CONFLICT (value, identifier_type, account_id)
132+
DO UPDATE SET trusted = email_trusted;
133+
END IF;
134+
RETURN account_id_;
135+
END;
136+
$$;

0 commit comments

Comments
 (0)