|
1 | 1 | import { NextResponse, NextRequest } from "next/server"; |
2 | | -import { |
3 | | - type PostgrestSingleResponse, |
4 | | - PostgrestError, |
5 | | - type User, |
6 | | -} from "@supabase/supabase-js"; |
7 | | -import { createClient } from "~/utils/supabase/server"; |
8 | | -import { getOrCreateEntity, ItemValidator } from "~/utils/supabase/dbUtils"; |
9 | 2 | import { |
10 | 3 | createApiResponse, |
11 | 4 | handleRouteError, |
12 | 5 | defaultOptionsHandler, |
13 | | - asPostgrestFailure, |
14 | 6 | } from "~/utils/supabase/apiUtils"; |
15 | | -import { Tables, TablesInsert } from "@repo/database/types.gen.ts"; |
16 | | -import { spaceAnonUserEmail } from "@repo/ui/lib/utils"; |
17 | | - |
18 | | -type SpaceDataInput = TablesInsert<"Space">; |
19 | | -type SpaceRecord = Tables<"Space">; |
20 | | - |
21 | | -type SpaceCreationInput = SpaceDataInput & { password: string }; |
22 | | - |
23 | | -const spaceValidator: ItemValidator<SpaceCreationInput> = (space) => { |
24 | | - if (!space || typeof space !== "object") |
25 | | - return "Invalid request body: expected a JSON object."; |
26 | | - const { name, url, platform, password } = space; |
27 | | - |
28 | | - if (!name || typeof name !== "string" || name.trim() === "") |
29 | | - return "Missing or invalid name."; |
30 | | - if (!url || typeof url !== "string" || url.trim() === "") |
31 | | - return "Missing or invalid URL."; |
32 | | - if (platform === undefined || !["Roam", "Obsidian"].includes(platform)) |
33 | | - return "Missing or invalid platform."; |
34 | | - if (!password || typeof password !== "string" || password.length < 8) |
35 | | - return "password must be at least 8 characters"; |
36 | | - return null; |
37 | | -}; |
38 | | - |
39 | | -const processAndGetOrCreateSpace = async ( |
40 | | - supabasePromise: ReturnType<typeof createClient>, |
41 | | - data: SpaceCreationInput, |
42 | | -): Promise<PostgrestSingleResponse<SpaceRecord>> => { |
43 | | - const { name, url, platform, password } = data; |
44 | | - const error = spaceValidator(data); |
45 | | - if (error !== null) return asPostgrestFailure(error, "invalid space"); |
46 | | - |
47 | | - const supabase = await supabasePromise; |
48 | | - |
49 | | - const result = await getOrCreateEntity<"Space">({ |
50 | | - supabase, |
51 | | - tableName: "Space", |
52 | | - insertData: { |
53 | | - name: name.trim(), |
54 | | - url: url.trim().replace(/\/$/, ""), |
55 | | - platform, |
56 | | - }, |
57 | | - uniqueOn: ["url"], |
58 | | - }); |
59 | | - if (result.error) return result; |
60 | | - const space_id = result.data.id; |
61 | | - |
62 | | - // this is related but each step is idempotent, so con retry w/o transaction |
63 | | - const email = spaceAnonUserEmail(platform, result.data.id); |
64 | | - let anonymousUser: User | null = null; |
65 | | - { |
66 | | - const { error, data } = await supabase.auth.signInWithPassword({ |
67 | | - email, |
68 | | - password, |
69 | | - }); |
70 | | - if (error && error.message !== "Invalid login credentials") { |
71 | | - // Handle unexpected errors |
72 | | - return asPostgrestFailure(error.message, "authentication_error"); |
73 | | - } |
74 | | - anonymousUser = data.user; |
75 | | - } |
76 | | - if (anonymousUser === null) { |
77 | | - const resultCreateAnonymousUser = await supabase.auth.admin.createUser({ |
78 | | - email, |
79 | | - password, |
80 | | - email_confirm: true, |
81 | | - }); |
82 | | - if (resultCreateAnonymousUser.error) { |
83 | | - return { |
84 | | - count: null, |
85 | | - status: resultCreateAnonymousUser.error.status || -1, |
86 | | - statusText: resultCreateAnonymousUser.error.message, |
87 | | - data: null, |
88 | | - error: new PostgrestError({ |
89 | | - message: resultCreateAnonymousUser.error.message, |
90 | | - details: |
91 | | - typeof resultCreateAnonymousUser.error.cause === "string" |
92 | | - ? resultCreateAnonymousUser.error.cause |
93 | | - : "", |
94 | | - hint: "", |
95 | | - code: resultCreateAnonymousUser.error.code || "unknown", |
96 | | - }), |
97 | | - }; // space created but not its user, try again |
98 | | - } |
99 | | - anonymousUser = resultCreateAnonymousUser.data.user; |
100 | | - } |
101 | | - // NOTE: The next few steps could be done as the new user, except the SpaceAccess |
102 | | - const anonPlatformUserResult = await getOrCreateEntity<"PlatformAccount">({ |
103 | | - supabase, |
104 | | - tableName: "PlatformAccount", |
105 | | - insertData: { |
106 | | - platform, |
107 | | - account_local_id: email, |
108 | | - name: `Anonymous of space ${space_id}`, |
109 | | - agent_type: "anonymous", |
110 | | - dg_account: anonymousUser.id, |
111 | | - }, |
112 | | - uniqueOn: ["account_local_id", "platform"], |
113 | | - }); |
114 | | - if (anonPlatformUserResult.error) return anonPlatformUserResult; |
115 | | - |
116 | | - const resultAnonUserSpaceAccess = await getOrCreateEntity<"SpaceAccess">({ |
117 | | - supabase, |
118 | | - tableName: "SpaceAccess", |
119 | | - insertData: { |
120 | | - space_id, |
121 | | - account_id: anonPlatformUserResult.data.id, |
122 | | - editor: true, |
123 | | - }, |
124 | | - uniqueOn: ["space_id", "account_id"], |
125 | | - }); |
126 | | - if (resultAnonUserSpaceAccess.error) return resultAnonUserSpaceAccess; // space created but not connected, try again |
127 | | - return result; |
128 | | -}; |
| 7 | +import { fetchOrCreateSpaceDirect } from "@repo/ui/lib/supabase/contextFunctions"; |
129 | 8 |
|
130 | 9 | export const POST = async (request: NextRequest): Promise<NextResponse> => { |
131 | | - const supabasePromise = createClient(); |
132 | | - |
133 | 10 | try { |
134 | | - const body: SpaceCreationInput = await request.json(); |
135 | | - const result = await processAndGetOrCreateSpace(supabasePromise, body); |
| 11 | + const body = await request.json(); |
| 12 | + const result = await fetchOrCreateSpaceDirect(body); |
136 | 13 | return createApiResponse(request, result); |
137 | 14 | } catch (e: unknown) { |
138 | 15 | return handleRouteError(request, e, "/api/supabase/space"); |
|
0 commit comments