Skip to content

Commit 0c92415

Browse files
committed
wip
1 parent 9116fef commit 0c92415

File tree

8 files changed

+335
-187
lines changed

8 files changed

+335
-187
lines changed

apps/postgres-new/app/api/deploy/route.ts

-121
This file was deleted.

apps/postgres-new/app/api/oauth/callback/route.ts

-46
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { createClient } from '~/utils/supabase/server'
2+
import { createClient as createAdminClient } from '~/utils/supabase/admin'
3+
import { NextRequest, NextResponse } from 'next/server'
4+
5+
export async function GET(req: NextRequest) {
6+
const supabase = createClient()
7+
8+
const { data, error } = await supabase.auth.getUser()
9+
10+
// We have middleware, so this should never happen (used for type narrowing)
11+
if (error) {
12+
return new Response('Unauthorized', { status: 401 })
13+
}
14+
15+
const { user } = data
16+
17+
const code = req.nextUrl.searchParams.get('code') as string | null
18+
19+
if (!code) {
20+
return new Response('No code provided', { status: 400 })
21+
}
22+
23+
const stateParam = req.nextUrl.searchParams.get('state')
24+
25+
if (!stateParam) {
26+
return new Response('No state provided', { status: 400 })
27+
}
28+
29+
const state = JSON.parse(stateParam)
30+
31+
if (!state.databaseId) {
32+
return new Response('No database id provided', { status: 400 })
33+
}
34+
35+
const now = Date.now()
36+
37+
// get tokens
38+
const tokensResponse = await fetch('https://api.supabase.com/v1/oauth/token', {
39+
method: 'POST',
40+
headers: {
41+
'Content-Type': 'application/x-www-form-urlencoded',
42+
Accept: 'application/json',
43+
Authorization: `Basic ${btoa(`${process.env.NEXT_PUBLIC_SUPABASE_OAUTH_CLIENT_ID}:${process.env.SUPABASE_OAUTH_SECRET}`)}`,
44+
},
45+
body: new URLSearchParams({
46+
grant_type: 'authorization_code',
47+
code,
48+
redirect_uri: req.nextUrl.origin + '/api/oauth/supabase/callback',
49+
}),
50+
})
51+
52+
const tokens = (await tokensResponse.json()) as {
53+
access_token: string
54+
refresh_token: string
55+
// usually 86400 seconds = 1 day
56+
expires_in: number
57+
token_type: 'Bearer'
58+
}
59+
60+
// get org
61+
const [org] = (await fetch('https://api.supabase.com/v1/organizations', {
62+
method: 'GET',
63+
headers: {
64+
Accept: 'application/json',
65+
Authorization: `Bearer ${tokens.access_token}`,
66+
},
67+
}).then((res) => res.json())) as {
68+
id: string
69+
name: string
70+
}[]
71+
72+
const adminClient = createAdminClient()
73+
74+
// store the tokens as secrets
75+
const { data: refreshTokenSecretId, error: refreshTokenSecretError } = await adminClient.rpc(
76+
'insert_secret',
77+
{
78+
name: `supabase_oauth_refresh_token_${org.id}`,
79+
secret: tokens.refresh_token,
80+
}
81+
)
82+
83+
if (refreshTokenSecretError) {
84+
return new Response('Failed to store refresh token as secret', { status: 500 })
85+
}
86+
const { data: accessTokenSecretId, error: accessTokenSecretError } = await adminClient.rpc(
87+
'insert_secret',
88+
{
89+
name: `supabase_oauth_access_token_${org.id}`,
90+
secret: tokens.access_token,
91+
}
92+
)
93+
94+
if (accessTokenSecretError) {
95+
return new Response('Failed to store access token as secret', { status: 500 })
96+
}
97+
98+
// store the credentials and relevant metadata
99+
const { data: deploymentProvider, error: deploymentProviderError } = await supabase
100+
.from('deployment_providers')
101+
.select('id')
102+
.eq('name', 'Supabase')
103+
.single()
104+
105+
if (deploymentProviderError) {
106+
return new Response('Failed to get deployment provider', { status: 500 })
107+
}
108+
109+
const integration = await supabase
110+
.from('deployment_provider_integrations')
111+
.insert({
112+
user_id: user.id,
113+
deployment_provider_id: deploymentProvider.id,
114+
credentials: {
115+
accessToken: accessTokenSecretId,
116+
expiresAt: new Date(now + tokens.expires_in * 1000).toISOString(),
117+
refreshToken: refreshTokenSecretId,
118+
},
119+
scope: {
120+
organizationId: org.id,
121+
},
122+
})
123+
.select('id')
124+
.single()
125+
126+
if (integration.error) {
127+
return new Response('Failed to create integration', { status: 500 })
128+
}
129+
130+
const params = new URLSearchParams({
131+
integration: integration.data.id.toString(),
132+
})
133+
134+
return NextResponse.redirect(new URL(`/deploy/${state.databaseId}?${params.toString()}`, req.url))
135+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
'use client'
2+
3+
import { useRouter } from 'next/navigation'
4+
import { useEffect } from 'react'
5+
import { useApp } from '~/components/app-provider'
6+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '~/components/ui/dialog'
7+
8+
export default function Page({ params }: { params: { databaseId: string } }) {
9+
const databaseId = params.databaseId
10+
const router = useRouter()
11+
const { dbManager, liveShare } = useApp()
12+
13+
useEffect(() => {
14+
async function run() {
15+
if (!dbManager) {
16+
throw new Error('dbManager is not available')
17+
}
18+
19+
try {
20+
await dbManager.getDbInstance(databaseId)
21+
} catch (err) {
22+
router.push('/')
23+
}
24+
25+
// make the database available to the deployment worker
26+
const databaseUrl = await liveShare.start(databaseId)
27+
28+
// trigger deployment
29+
}
30+
run()
31+
return () => {
32+
liveShare.stop()
33+
}
34+
}, [dbManager, databaseId, router, liveShare])
35+
36+
return (
37+
<Dialog open>
38+
<DialogContent className="max-w-2xl" showCloseButton={false}>
39+
<DialogHeader>
40+
<DialogTitle>Deploying your database</DialogTitle>
41+
<div className="py-2 border-b" />
42+
</DialogHeader>
43+
<div>
44+
<p>Your database is being deployed. Please do not close this page.</p>
45+
</div>
46+
</DialogContent>
47+
</Dialog>
48+
)
49+
}

apps/postgres-new/components/app-provider.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ export default function AppProvider({ children }: AppProps) {
193193
}
194194

195195
setLiveShareWebsocket(ws)
196+
197+
const databaseUrl = `postgres://postgres@${databaseHostname}/postgres?sslmode=require`
198+
199+
return databaseUrl
196200
},
197201
[cleanUp, supabase.auth]
198202
)
@@ -268,7 +272,7 @@ export type AppContextValues = {
268272
pgliteVersion?: string
269273
pgVersion?: string
270274
liveShare: {
271-
start: (databaseId: string) => Promise<void>
275+
start: (databaseId: string) => Promise<string>
272276
stop: () => void
273277
databaseId: string | null
274278
clientIp: string | null

0 commit comments

Comments
 (0)