diff --git a/README.md b/README.md index 79d263d5d5..5b198bc655 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,19 @@ npm install supabase --save-dev npx supabase start ``` +Run migration script to create the tables + +```bash +psql -h db.your-supabase-instance.supabase.co -U your_supabase_user -d your_supabase_database -f supabase/migrations/20230707053030_init.sql +``` + +Create the table in Supabase UI + +``` +Table name: chats +Columns: id (default Supabase), convo_id (text, primary, unique), payload (jsonb), user_id (uuid) +``` + Install the local dependencies and start dev mode: ```bash diff --git a/app/actions.ts b/app/actions.ts index 6f709503b3..d6f21fbb0f 100644 --- a/app/actions.ts +++ b/app/actions.ts @@ -1,6 +1,6 @@ 'use server' import 'server-only' -import { createServerActionClient } from '@supabase/auth-helpers-nextjs' +import { createServerClient, type CookieOptions } from '@supabase/ssr' import { cookies } from 'next/headers' import { Database } from '@/lib/db_types' import { revalidatePath } from 'next/cache' @@ -14,9 +14,24 @@ export async function getChats(userId?: string | null) { } try { const cookieStore = cookies() - const supabase = createServerActionClient({ - cookies: () => cookieStore - }) + + const supabase = createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + get(name: string) { + return cookieStore.get(name)?.value + }, + set(name: string, value: string, options: CookieOptions) { + cookieStore.set({ name, value, ...options }) + }, + remove(name: string, options: CookieOptions) { + cookieStore.set({ name, value: '', ...options }) + }, + }, + }); + const { data } = await supabase .from('chats') .select('payload') @@ -32,13 +47,26 @@ export async function getChats(userId?: string | null) { export async function getChat(id: string) { const cookieStore = cookies() - const supabase = createServerActionClient({ - cookies: () => cookieStore - }) + const supabase = createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + get(name: string) { + return cookieStore.get(name)?.value + }, + set(name: string, value: string, options: CookieOptions) { + cookieStore.set({ name, value, ...options }) + }, + remove(name: string, options: CookieOptions) { + cookieStore.set({ name, value: '', ...options }) + }, + }, + }); const { data } = await supabase .from('chats') .select('payload') - .eq('id', id) + .eq('convo_id', id) .maybeSingle() return (data?.payload as Chat) ?? null @@ -47,10 +75,23 @@ export async function getChat(id: string) { export async function removeChat({ id, path }: { id: string; path: string }) { try { const cookieStore = cookies() - const supabase = createServerActionClient({ - cookies: () => cookieStore - }) - await supabase.from('chats').delete().eq('id', id).throwOnError() + const supabase = createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + get(name: string) { + return cookieStore.get(name)?.value + }, + set(name: string, value: string, options: CookieOptions) { + cookieStore.set({ name, value, ...options }) + }, + remove(name: string, options: CookieOptions) { + cookieStore.set({ name, value: '', ...options }) + }, + }, + }); + await supabase.from('chats').delete().eq('convo_id', id).throwOnError() revalidatePath('/') return revalidatePath(path) @@ -64,9 +105,22 @@ export async function removeChat({ id, path }: { id: string; path: string }) { export async function clearChats() { try { const cookieStore = cookies() - const supabase = createServerActionClient({ - cookies: () => cookieStore - }) + const supabase = createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + get(name: string) { + return cookieStore.get(name)?.value + }, + set(name: string, value: string, options: CookieOptions) { + cookieStore.set({ name, value, ...options }) + }, + remove(name: string, options: CookieOptions) { + cookieStore.set({ name, value: '', ...options }) + }, + }, + }); await supabase.from('chats').delete().throwOnError() revalidatePath('/') return redirect('/') @@ -80,13 +134,26 @@ export async function clearChats() { export async function getSharedChat(id: string) { const cookieStore = cookies() - const supabase = createServerActionClient({ - cookies: () => cookieStore - }) + const supabase = createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + get(name: string) { + return cookieStore.get(name)?.value + }, + set(name: string, value: string, options: CookieOptions) { + cookieStore.set({ name, value, ...options }) + }, + remove(name: string, options: CookieOptions) { + cookieStore.set({ name, value: '', ...options }) + }, + }, + }); const { data } = await supabase .from('chats') .select('payload') - .eq('id', id) + .eq('convo_id', id) .not('payload->sharePath', 'is', null) .maybeSingle() @@ -100,13 +167,26 @@ export async function shareChat(chat: Chat) { } const cookieStore = cookies() - const supabase = createServerActionClient({ - cookies: () => cookieStore - }) + const supabase = createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + get(name: string) { + return cookieStore.get(name)?.value + }, + set(name: string, value: string, options: CookieOptions) { + cookieStore.set({ name, value, ...options }) + }, + remove(name: string, options: CookieOptions) { + cookieStore.set({ name, value: '', ...options }) + }, + }, + }); await supabase .from('chats') .update({ payload: payload as any }) - .eq('id', chat.id) + .eq('convo_id', chat.id) .throwOnError() return payload diff --git a/app/api/auth/callback/route.ts b/app/api/auth/callback/route.ts index d57da5ad39..740c523966 100644 --- a/app/api/auth/callback/route.ts +++ b/app/api/auth/callback/route.ts @@ -1,5 +1,5 @@ import 'server-only' -import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' +import { createServerClient, type CookieOptions } from '@supabase/ssr' import { cookies } from 'next/headers' import { NextResponse } from 'next/server' @@ -12,9 +12,23 @@ export async function GET(request: Request) { if (code) { const cookieStore = cookies() - const supabase = createRouteHandlerClient({ - cookies: () => cookieStore - }) + const supabase = createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + get(name: string) { + return cookieStore.get(name)?.value + }, + set(name: string, value: string, options: CookieOptions) { + cookieStore.set({ name, value, ...options }) + }, + remove(name: string, options: CookieOptions) { + cookieStore.set({ name, value: '', ...options }) + }, + }, + } + ) await supabase.auth.exchangeCodeForSession(code) } diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 4d58613891..6c356cb32f 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -1,7 +1,7 @@ import 'server-only' import { OpenAIStream, StreamingTextResponse } from 'ai' import { Configuration, OpenAIApi } from 'openai-edge' -import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' +import { createServerClient, type CookieOptions } from '@supabase/ssr' import { cookies } from 'next/headers' import { Database } from '@/lib/db_types' @@ -18,12 +18,25 @@ const openai = new OpenAIApi(configuration) export async function POST(req: Request) { const cookieStore = cookies() - const supabase = createRouteHandlerClient({ - cookies: () => cookieStore - }) + const supabase = createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + get(name: string) { + return cookieStore.get(name)?.value + }, + set(name: string, value: string, options: CookieOptions) { + cookieStore.set({ name, value, ...options }) + }, + remove(name: string, options: CookieOptions) { + cookieStore.set({ name, value: '', ...options }) + }, + }, + }) const json = await req.json() const { messages, previewToken } = json - const userId = (await auth({ cookieStore }))?.user.id + const userId = (await auth({ cookieStore }))?.id if (!userId) { return new Response('Unauthorized', { @@ -62,8 +75,9 @@ export async function POST(req: Request) { } ] } - // Insert chat into database. - await supabase.from('chats').upsert({ id, payload }).throwOnError() + + // @ts-expect-error upsert doesn't like convo_id somehow + await supabase.from('chats').upsert({ convo_id: id, payload }, { onConflict: 'convo_id' }).throwOnError() } }) diff --git a/app/chat/[id]/page.tsx b/app/chat/[id]/page.tsx index fd733130ed..d5c55d4dab 100644 --- a/app/chat/[id]/page.tsx +++ b/app/chat/[id]/page.tsx @@ -19,9 +19,9 @@ export async function generateMetadata({ params }: ChatPageProps): Promise { const cookieStore = cookies() - const session = await auth({ cookieStore }) + const user = await auth({ cookieStore }) - if (!session?.user) { + if (!user.id) { return {} } @@ -33,9 +33,9 @@ export async function generateMetadata({ export default async function ChatPage({ params }: ChatPageProps) { const cookieStore = cookies() - const session = await auth({ cookieStore }) + const user = await auth({ cookieStore }) - if (!session?.user) { + if (!user) { redirect(`/sign-in?next=/chat/${params.id}`) } @@ -45,7 +45,7 @@ export default async function ChatPage({ params }: ChatPageProps) { notFound() } - if (chat?.userId !== session?.user?.id) { + if (chat?.userId !== user?.id) { notFound() } diff --git a/app/sign-in/page.tsx b/app/sign-in/page.tsx index 1a3e8aa8c4..950fe5e403 100644 --- a/app/sign-in/page.tsx +++ b/app/sign-in/page.tsx @@ -7,9 +7,9 @@ import { redirect } from 'next/navigation' export default async function SignInPage() { const cookieStore = cookies() - const session = await auth({ cookieStore }) + const user = await auth({ cookieStore }) // redirect to home if user is already logged in - if (session?.user) { + if (user) { redirect('/') } return ( diff --git a/app/sign-up/page.tsx b/app/sign-up/page.tsx index d4979d21a5..76b8492ba2 100644 --- a/app/sign-up/page.tsx +++ b/app/sign-up/page.tsx @@ -7,9 +7,9 @@ import { redirect } from 'next/navigation' export default async function SignInPage() { const cookieStore = cookies() - const session = await auth({ cookieStore }) + const user = await auth({ cookieStore }) // redirect to home if user is already logged in - if (session?.user) { + if (user) { redirect('/') } return ( diff --git a/auth.ts b/auth.ts index dc1ea5683a..1a8f587cbf 100644 --- a/auth.ts +++ b/auth.ts @@ -1,5 +1,5 @@ import 'server-only' -import { createServerComponentClient } from '@supabase/auth-helpers-nextjs' +import { createServerClient, type CookieOptions } from '@supabase/ssr' import { cookies } from 'next/headers' export const auth = async ({ @@ -8,10 +8,19 @@ export const auth = async ({ cookieStore: ReturnType }) => { // Create a Supabase client configured to use cookies - const supabase = createServerComponentClient({ - cookies: () => cookieStore - }) - const { data, error } = await supabase.auth.getSession() + + const supabase = createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + get(name: string) { + return cookieStore.get(name)?.value + }, + }, + } + ) + const { data, error } = await supabase.auth.getUser() if (error) throw error - return data.session + return data.user } diff --git a/components/header.tsx b/components/header.tsx index 35e027b2b7..c93d85d71f 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -21,15 +21,15 @@ import { cookies } from 'next/headers' export async function Header() { const cookieStore = cookies() - const session = await auth({ cookieStore }) + const user = await auth({ cookieStore }) return (
- {session?.user ? ( + {user ? ( }> {/* @ts-ignore */} - + @@ -44,8 +44,8 @@ export async function Header() { )}
- {session?.user ? ( - + {user ? ( + ) : (