Skip to content

Commit 2a68048

Browse files
authored
Merge pull request #1 from supabase-community/supabaseify
Supabaseify
2 parents 012ea40 + 045b0b9 commit 2a68048

25 files changed

+837
-311
lines changed

.env.example

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,12 @@
22
## Then get your OpenAI API Key here: https://platform.openai.com/account/api-keys
33
OPENAI_API_KEY=XXXXXXXX
44

5-
## Generate a random secret: https://generate-secret.vercel.app/32 or `openssl rand -base64 32`
6-
AUTH_SECRET=XXXXXXXX
7-
## Create a GitHub OAuth app here: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app
5+
# Update these with your Supabase details from your project settings > API
6+
# https://app.supabase.com/project/_/settings/api
7+
# In local dev you can get these by running `supabase status`.
8+
NEXT_PUBLIC_SUPABASE_URL=your-project-url
9+
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
10+
11+
## Follow GitHub Oauth setup steps from Supabase:
812
AUTH_GITHUB_ID=XXXXXXXX
913
AUTH_GITHUB_SECRET=XXXXXXXX
10-
## Support OAuth login on preview deployments, see: https://authjs.dev/guides/basics/deployment#securing-a-preview-deployment
11-
AUTH_REDIRECT_PROXY_URL=https://auth.example.com/api/auth
12-
13-
# Instructions to create kv database here: https://vercel.com/docs/storage/vercel-kv/quickstart and
14-
KV_URL=XXXXXXXX
15-
KV_REST_API_URL=XXXXXXXX
16-
KV_REST_API_TOKEN=XXXXXXXX
17-
KV_REST_API_READ_ONLY_TOKEN=XXXXXXXX
18-

README.md

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
</a>
55

66
<p align="center">
7-
An open-source AI chatbot app template built with Next.js, the Vercel AI SDK, OpenAI, and Vercel KV.
7+
An open-source AI chatbot app template built with Next.js, the Vercel AI SDK, OpenAI, and Supabase Auth and Postgres DB.
88
</p>
99

1010
<p align="center">
@@ -27,35 +27,40 @@
2727
- Styling with [Tailwind CSS](https://tailwindcss.com)
2828
- [Radix UI](https://radix-ui.com) for headless component primitives
2929
- Icons from [Phosphor Icons](https://phosphoricons.com)
30-
- Chat History, rate limiting, and session storage with [Vercel KV](https://vercel.com/storage/kv)
31-
- [Next Auth](https://github.com/nextauthjs/next-auth) for authentication
30+
- Chat History with [Supabase Postgres DB](https://supabase.com)
31+
- [Supabase Auth](https://supabase.com/auth) for authentication
3232

3333
## Model Providers
3434

3535
This template ships with OpenAI `gpt-3.5-turbo` as the default. However, thanks to the [Vercel AI SDK](https://sdk.vercel.ai/docs), you can switch LLM providers to [Anthropic](https://anthropic.com), [Hugging Face](https://huggingface.co), or using [LangChain](https://js.langchain.com) with just a few lines of code.
3636

37-
## Deploy Your Own
37+
<!-- ## Deploy Your Own
3838
3939
You can deploy your own version of the Next.js AI Chatbot to Vercel with one click:
4040
41-
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?demo-title=Next.js+Chat&demo-description=A+full-featured%2C+hackable+Next.js+AI+chatbot+built+by+Vercel+Labs&demo-url=https%3A%2F%2Fchat.vercel.ai%2F&demo-image=%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F4aVPvWuTmBvzM5cEdRdqeW%2F4234f9baf160f68ffb385a43c3527645%2FCleanShot_2023-06-16_at_17.09.21.png&project-name=Next.js+Chat&repository-name=nextjs-chat&repository-url=https%3A%2F%2Fgithub.com%2Fvercel-labs%2Fai-chatbot&from=templates&skippable-integrations=1&env=OPENAI_API_KEY%2CAUTH_GITHUB_ID%2CAUTH_GITHUB_SECRET&envDescription=How+to+get+these+env+vars&envLink=https%3A%2F%2Fgithub.com%2Fvercel-labs%2Fai-chatbot%2Fblob%2Fmain%2F.env.example&teamCreateStatus=hidden&stores=[{"type":"kv"}])
41+
TODO: update button with supabase integration
42+
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?demo-title=Next.js+Chat&demo-description=A+full-featured%2C+hackable+Next.js+AI+chatbot+built+by+Vercel+Labs&demo-url=https%3A%2F%2Fchat.vercel.ai%2F&demo-image=%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F4aVPvWuTmBvzM5cEdRdqeW%2F4234f9baf160f68ffb385a43c3527645%2FCleanShot_2023-06-16_at_17.09.21.png&project-name=Next.js+Chat&repository-name=nextjs-chat&repository-url=https%3A%2F%2Fgithub.com%2Fvercel-labs%2Fai-chatbot&from=templates&skippable-integrations=1&env=OPENAI_API_KEY%2CAUTH_GITHUB_ID%2CAUTH_GITHUB_SECRET&envDescription=How+to+get+these+env+vars&envLink=https%3A%2F%2Fgithub.com%2Fvercel-labs%2Fai-chatbot%2Fblob%2Fmain%2F.env.example&teamCreateStatus=hidden&stores=[{"type":"kv"}]) -->
4243

43-
## Creating a KV Database Instance
44+
## Running locally
4445

45-
Follow the steps outlined in the [quick start guide](https://vercel.com/docs/storage/vercel-kv/quickstart#create-a-kv-database) provided by Vercel. This guide will assist you in creating and configuring your KV database instance on Vercel, enabling your application to interact with it.
46+
You will need to use the environment variables [defined in `.env.example`](.env.example) to run Next.js AI Chatbot. It's recommended you use [Vercel Environment Variables](https://vercel.com/docs/concepts/projects/environment-variables) for this, but a `.env` file is all that is necessary.
4647

47-
Remember to update your environment variables (`KV_URL`, `KV_REST_API_URL`, `KV_REST_API_TOKEN`, `KV_REST_API_READ_ONLY_TOKEN`) in the `.env` file with the appropriate credentials provided during the KV database setup.
48+
> Note: You should not commit your `.env` file or it will expose secrets that will allow others to control access to your various OpenAI and authentication provider accounts.
4849
50+
Copy the `.env.example` file and populate the required env vars:
4951

50-
## Running locally
52+
```bash
53+
cp .env.example .env
54+
```
5155

52-
You will need to use the environment variables [defined in `.env.example`](.env.example) to run Next.js AI Chatbot. It's recommended you use [Vercel Environment Variables](https://vercel.com/docs/concepts/projects/environment-variables) for this, but a `.env` file is all that is necessary.
56+
[Install the Supabase CLI](https://supabase.com/docs/guides/cli) and start the local Supabase stack:
5357

54-
> Note: You should not commit your `.env` file or it will expose secrets that will allow others to control access to your various OpenAI and authentication provider accounts.
58+
```bash
59+
npm install supabase --save-dev
60+
npx supabase start
61+
```
5562

56-
1. Install Vercel CLI: `npm i -g vercel`
57-
2. Link local instance with Vercel and GitHub accounts (creates `.vercel` directory): `vercel link`
58-
3. Download your environment variables: `vercel env pull`
63+
Install the local dependencies and start dev mode:
5964

6065
```bash
6166
pnpm install
@@ -71,3 +76,4 @@ This library is created by [Vercel](https://vercel.com) and [Next.js](https://ne
7176
- Jared Palmer ([@jaredpalmer](https://twitter.com/jaredpalmer)) - [Vercel](https://vercel.com)
7277
- Shu Ding ([@shuding\_](https://twitter.com/shuding_)) - [Vercel](https://vercel.com)
7378
- shadcn ([@shadcn](https://twitter.com/shadcn)) - [Contractor](https://shadcn.com)
79+
- Thor Schaeff ([@thorwebdev](https://twitter.com/thorwebdev)) - [Supabaseifier](https://thor.bio)

app/actions.ts

Lines changed: 48 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,97 @@
11
'use server'
22

3+
import { createServerActionClient } from '@supabase/auth-helpers-nextjs'
4+
import { cookies } from 'next/headers'
5+
import { Database } from '@/lib/db_types'
36
import { revalidatePath } from 'next/cache'
47
import { redirect } from 'next/navigation'
5-
import { kv } from '@vercel/kv'
68

7-
import { auth } from '@/auth'
89
import { type Chat } from '@/lib/types'
10+
import { auth } from '@/auth'
11+
12+
const supabase = createServerActionClient<Database>({ cookies })
913

1014
export async function getChats(userId?: string | null) {
1115
if (!userId) {
1216
return []
1317
}
1418

1519
try {
16-
const pipeline = kv.pipeline()
17-
const chats: string[] = await kv.zrange(`user:chat:${userId}`, 0, -1, {
18-
rev: true
19-
})
20-
21-
for (const chat of chats) {
22-
pipeline.hgetall(chat)
23-
}
20+
const { data } = await supabase
21+
.from('chats')
22+
.select('payload')
23+
.order('payload->createdAt', { ascending: false })
24+
.throwOnError()
2425

25-
const results = await pipeline.exec()
26-
27-
return results as Chat[]
26+
return (data?.map(entry => entry.payload) as Chat[]) ?? []
2827
} catch (error) {
2928
return []
3029
}
3130
}
3231

33-
export async function getChat(id: string, userId: string) {
34-
const chat = await kv.hgetall<Chat>(`chat:${id}`)
35-
36-
if (!chat || (userId && chat.userId !== userId)) {
37-
return null
38-
}
32+
export async function getChat(id: string) {
33+
const { data } = await supabase
34+
.from('chats')
35+
.select('payload')
36+
.eq('id', id)
37+
.maybeSingle()
3938

40-
return chat
39+
return (data?.payload as Chat) ?? null
4140
}
4241

4342
export async function removeChat({ id, path }: { id: string; path: string }) {
44-
const session = await auth()
45-
46-
if (!session) {
47-
return {
48-
error: 'Unauthorized'
49-
}
50-
}
51-
52-
const uid = await kv.hget<string>(`chat:${id}`, 'userId')
43+
try {
44+
await supabase.from('chats').delete().eq('id', id).throwOnError()
5345

54-
if (uid !== session?.user?.id) {
46+
revalidatePath('/')
47+
return revalidatePath(path)
48+
} catch (error) {
5549
return {
5650
error: 'Unauthorized'
5751
}
5852
}
59-
60-
await kv.del(`chat:${id}`)
61-
await kv.zrem(`user:chat:${session.user.id}`, `chat:${id}`)
62-
63-
revalidatePath('/')
64-
return revalidatePath(path)
6553
}
6654

6755
export async function clearChats() {
68-
const session = await auth()
69-
70-
if (!session?.user?.id) {
56+
try {
57+
const session = await auth()
58+
await supabase
59+
.from('chats')
60+
.delete()
61+
.eq('user_id', session?.user.id)
62+
.throwOnError()
63+
revalidatePath('/')
64+
return redirect('/')
65+
} catch (error) {
66+
console.log('clear chats error', error)
7167
return {
7268
error: 'Unauthorized'
7369
}
7470
}
75-
76-
const chats: string[] = await kv.zrange(`user:chat:${session.user.id}`, 0, -1)
77-
if (!chats.length) {
78-
return redirect('/')
79-
}
80-
const pipeline = kv.pipeline()
81-
82-
for (const chat of chats) {
83-
pipeline.del(chat)
84-
pipeline.zrem(`user:chat:${session.user.id}`, chat)
85-
}
86-
87-
await pipeline.exec()
88-
89-
revalidatePath('/')
90-
return redirect('/')
9171
}
9272

9373
export async function getSharedChat(id: string) {
94-
const chat = await kv.hgetall<Chat>(`chat:${id}`)
95-
96-
if (!chat || !chat.sharePath) {
97-
return null
98-
}
99-
100-
return chat
74+
const { data } = await supabase
75+
.from('chats')
76+
.select('payload')
77+
.eq('id', id)
78+
.not('payload->sharePath', 'is', null)
79+
.maybeSingle()
80+
81+
return (data?.payload as Chat) ?? null
10182
}
10283

10384
export async function shareChat(chat: Chat) {
104-
const session = await auth()
105-
106-
if (!session?.user?.id || session.user.id !== chat.userId) {
107-
return {
108-
error: 'Unauthorized'
109-
}
110-
}
111-
11285
const payload = {
11386
...chat,
11487
sharePath: `/share/${chat.id}`
11588
}
11689

117-
await kv.hmset(`chat:${chat.id}`, payload)
90+
await supabase
91+
.from('chats')
92+
.update({ payload: payload as any })
93+
.eq('id', chat.id)
94+
.throwOnError()
11895

11996
return payload
12097
}

app/api/auth/[...nextauth]/route.ts

Lines changed: 0 additions & 2 deletions
This file was deleted.

app/api/auth/callback/route.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
2+
import { cookies } from 'next/headers'
3+
import { NextResponse } from 'next/server'
4+
5+
export async function GET(request: Request) {
6+
// The `/auth/callback` route is required for the server-side auth flow implemented
7+
// by the Auth Helpers package. It exchanges an auth code for the user's session.
8+
// https://supabase.com/docs/guides/auth/auth-helpers/nextjs#managing-sign-in-with-code-exchange
9+
const requestUrl = new URL(request.url)
10+
const code = requestUrl.searchParams.get('code')
11+
12+
if (code) {
13+
const supabase = createRouteHandlerClient({ cookies })
14+
await supabase.auth.exchangeCodeForSession(code)
15+
}
16+
17+
// URL to redirect to after sign in process completes
18+
return NextResponse.redirect(requestUrl.origin)
19+
}

app/api/chat/route.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { kv } from '@vercel/kv'
21
import { OpenAIStream, StreamingTextResponse } from 'ai'
32
import { Configuration, OpenAIApi } from 'openai-edge'
3+
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
4+
import { cookies } from 'next/headers'
5+
import { Database } from '@/lib/db_types'
46

57
import { auth } from '@/auth'
68
import { nanoid } from '@/lib/utils'
@@ -14,6 +16,7 @@ const configuration = new Configuration({
1416
const openai = new OpenAIApi(configuration)
1517

1618
export async function POST(req: Request) {
19+
const supabase = createRouteHandlerClient<Database>({ cookies })
1720
const json = await req.json()
1821
const { messages, previewToken } = json
1922
const userId = (await auth())?.user.id
@@ -55,11 +58,8 @@ export async function POST(req: Request) {
5558
}
5659
]
5760
}
58-
await kv.hmset(`chat:${id}`, payload)
59-
await kv.zadd(`user:chat:${userId}`, {
60-
score: createdAt,
61-
member: `chat:${id}`
62-
})
61+
// Insert chat into database.
62+
await supabase.from('chats').upsert({ id, payload }).throwOnError()
6363
}
6464
})
6565

app/chat/[id]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export async function generateMetadata({
2323
return {}
2424
}
2525

26-
const chat = await getChat(params.id, session.user.id)
26+
const chat = await getChat(params.id)
2727
return {
2828
title: chat?.title.toString().slice(0, 50) ?? 'Chat'
2929
}
@@ -36,7 +36,7 @@ export default async function ChatPage({ params }: ChatPageProps) {
3636
redirect(`/sign-in?next=/chat/${params.id}`)
3737
}
3838

39-
const chat = await getChat(params.id, session.user.id)
39+
const chat = await getChat(params.id)
4040

4141
if (!chat) {
4242
notFound()

app/share/[id]/opengraph-image.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export default async function Image({ params }: ImageProps) {
9090
<div tw="flex text-[1.8rem] ml-4 text-[#9b9ba4]">
9191
Built with{' '}
9292
<div tw="flex text-[#eaeaf0] ml-2 mr-2">Vercel AI SDK</div> &
93-
<div tw="flex text-[#eaeaf0] ml-2">KV</div>
93+
<div tw="flex text-[#eaeaf0] ml-2">Supabase Auth & DB</div>
9494
</div>
9595
</div>
9696
<div tw="text-[1.8rem] ml-auto text-[#9b9ba4]">chat.vercel.ai</div>

auth.ts

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,10 @@
1-
import NextAuth, { type DefaultSession } from 'next-auth'
2-
import GitHub from 'next-auth/providers/github'
3-
import { NextResponse } from 'next/server'
1+
import { createServerActionClient } from '@supabase/auth-helpers-nextjs'
2+
import { cookies } from 'next/headers'
43

5-
declare module 'next-auth' {
6-
interface Session {
7-
user: {
8-
/** The user's id. */
9-
id: string
10-
} & DefaultSession['user']
11-
}
4+
export const auth = async () => {
5+
// Create a Supabase client configured to use cookies
6+
const supabase = createServerActionClient({ cookies })
7+
const { data, error } = await supabase.auth.getSession()
8+
if (error) throw error
9+
return data.session
1210
}
13-
14-
export const {
15-
handlers: { GET, POST },
16-
auth,
17-
CSRF_experimental
18-
} = NextAuth({
19-
providers: [GitHub],
20-
callbacks: {
21-
jwt({ token, profile }) {
22-
if (profile) {
23-
token.id = profile.id
24-
token.image = profile.picture
25-
}
26-
return token
27-
},
28-
authorized({ auth }) {
29-
return !!auth?.user
30-
}
31-
},
32-
pages: {
33-
signIn: '/sign-in'
34-
}
35-
})

0 commit comments

Comments
 (0)