diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..787a424 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +NEXT_PUBLIC_SUPABASE_URL= +NEXT_PUBLIC_SUPABASE_ANON_KEY= \ No newline at end of file diff --git a/app/(auth)/auth/callback/route.ts b/app/(auth)/auth/callback/route.ts new file mode 100644 index 0000000..819d676 --- /dev/null +++ b/app/(auth)/auth/callback/route.ts @@ -0,0 +1,19 @@ +import type { NextRequest } from 'next/server'; + +import { createSupabaseServerClient } from '@/lib/services/supabase/server'; +import { cookies } from 'next/headers'; +import { NextResponse } from 'next/server'; + +export async function GET(request: NextRequest) { + const requestUrl = new URL(request.url); + const code = requestUrl.searchParams.get('code'); + if (code) { + const cookieStore = cookies(); + const supabase = createSupabaseServerClient(cookieStore); + + await supabase.auth.exchangeCodeForSession(code); + } + + // URL to redirect to after sign in process completes + return NextResponse.redirect(requestUrl.origin); +} diff --git a/app/(auth)/auth/sign-in/route.ts b/app/(auth)/auth/sign-in/route.ts new file mode 100644 index 0000000..5b1b834 --- /dev/null +++ b/app/(auth)/auth/sign-in/route.ts @@ -0,0 +1,23 @@ +import type { NextRequest } from 'next/server'; + +import { createSupabaseServerClient } from '@/lib/services/supabase/server'; +import { cookies } from 'next/headers'; +import { NextResponse } from 'next/server'; + +export async function POST(request: NextRequest) { + const supabase = createSupabaseServerClient(cookies()); + const redirectURL = new URL('/auth/callback', request.url); + + const scopes = 'user-read-email user-read-playback-position'; + const result = await supabase.auth.signInWithOAuth({ + options: { + redirectTo: redirectURL.toString(), + scopes, + }, + provider: 'spotify', + }); + + return NextResponse.redirect(result.data.url!, { + status: 301, + }); +} diff --git a/app/(auth)/sign-in/page.tsx b/app/(auth)/sign-in/page.tsx new file mode 100644 index 0000000..468b058 --- /dev/null +++ b/app/(auth)/sign-in/page.tsx @@ -0,0 +1,9 @@ +import { Button } from '@radix-ui/themes'; + +export default function Page() { + return ( +
+ +
+ ); +} diff --git a/env.mjs b/env.mjs index 5793354..32ea9a1 100644 --- a/env.mjs +++ b/env.mjs @@ -16,6 +16,11 @@ const getGitSha = () => { const envSchema = z.object({ GIT_SHA: z.string().min(1).default(getGitSha()), + NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string(), + NEXT_PUBLIC_SUPABASE_URL: z.string(), + // We don't have these configurations yet. + SUPABASE_SERVICE_ROLE_KEY: z.string().default(''), + SUPABASE_URL: z.string().default(''), }); const envValidation = envSchema.safeParse(process.env); diff --git a/lib/services/supabase/auth.ts b/lib/services/supabase/auth.ts new file mode 100644 index 0000000..89b4a88 --- /dev/null +++ b/lib/services/supabase/auth.ts @@ -0,0 +1,63 @@ +import type { Database } from '@/types/database'; +import type { CookieOptions } from '@supabase/ssr'; +import type { NextRequest } from 'next/server'; + +import { env } from '@/env.mjs'; +import { createServerClient } from '@supabase/ssr'; +import { NextResponse } from 'next/server'; + +export const getSupabaseAuthSession = async (request: NextRequest) => { + let response = NextResponse.next({ + request: { + headers: request.headers, + }, + }); + + const supabase = createServerClient( + env.NEXT_PUBLIC_SUPABASE_URL, + env.NEXT_PUBLIC_SUPABASE_ANON_KEY, + { + cookies: { + get(name: string) { + return request.cookies.get(name)?.value; + }, + remove(name, options) { + request.cookies.delete({ + name, + ...options, + }); + response = NextResponse.next({ + request: { + headers: request.headers, + }, + }); + response.cookies.delete({ + name, + ...options, + }); + }, + set(name: string, value: string, options: CookieOptions) { + request.cookies.set({ + name, + value, + ...options, + }); + response = NextResponse.next({ + request: { + headers: request.headers, + }, + }); + response.cookies.set({ + name, + value, + ...options, + }); + }, + }, + }, + ); + + const authSession = await supabase.auth.getSession(); + + return { authSession, response }; +}; diff --git a/lib/services/supabase/browser.ts b/lib/services/supabase/browser.ts new file mode 100644 index 0000000..dbcd75e --- /dev/null +++ b/lib/services/supabase/browser.ts @@ -0,0 +1,11 @@ +'use client'; +import type { Database } from '@/types/database'; + +import { env } from '@/env.mjs'; +import { createBrowserClient } from '@supabase/ssr'; + +export const createSupabaseBrowserClient = () => + createBrowserClient( + env.NEXT_PUBLIC_SUPABASE_URL, + env.NEXT_PUBLIC_SUPABASE_ANON_KEY, + ); diff --git a/lib/services/supabase/server.ts b/lib/services/supabase/server.ts new file mode 100644 index 0000000..92e5c46 --- /dev/null +++ b/lib/services/supabase/server.ts @@ -0,0 +1,39 @@ +import type { Database } from '@/types/database'; +import type { CookieOptions } from '@supabase/ssr'; +import type { cookies } from 'next/headers'; + +import { env } from '@/env.mjs'; +import { createServerClient } from '@supabase/ssr'; + +export const createSupabaseServerClient = ( + cookieStore: ReturnType, +) => + createServerClient( + env.NEXT_PUBLIC_SUPABASE_URL, + env.NEXT_PUBLIC_SUPABASE_ANON_KEY, + { + cookies: { + get(name: string) { + return cookieStore.get(name)?.value; + }, + remove(name: string, options: CookieOptions) { + try { + cookieStore.delete({ name, ...options }); + } catch (error) { + // The `delete` method was called from a Server Component. + // This can be ignored if you have middleware refreshing + // user sessions. + } + }, + set(name: string, value: string, options: CookieOptions) { + try { + cookieStore.set({ name, value, ...options }); + } catch (error) { + // The `set` method was called from a Server Component. + // This can be ignored if you have middleware refreshing + // user sessions. + } + }, + }, + }, + ); diff --git a/lib/services/supabase/service.ts b/lib/services/supabase/service.ts new file mode 100644 index 0000000..348ba40 --- /dev/null +++ b/lib/services/supabase/service.ts @@ -0,0 +1,7 @@ +import type { Database } from '@/types/database'; + +import { env } from '@/env.mjs'; +import { createClient } from '@supabase/supabase-js'; + +export const createSupabaseServiceClient = () => + createClient(env.SUPABASE_URL, env.SUPABASE_SERVICE_ROLE_KEY); diff --git a/middleware.ts b/middleware.ts new file mode 100644 index 0000000..3b8d15b --- /dev/null +++ b/middleware.ts @@ -0,0 +1,29 @@ +import type { NextRequest } from 'next/server'; + +import { getSupabaseAuthSession } from '@/lib/services/supabase/auth'; +import { NextResponse } from 'next/server'; + +const publicRoutes = ['/', '/auth/callback', '/auth/sign-in', '/sign-in']; + +export async function middleware(request: NextRequest) { + const { pathname } = new URL(request.url); + + if (publicRoutes.includes(pathname)) { + return NextResponse.next({ + request: { + headers: request.headers, + }, + }); + } + + const { authSession, response } = await getSupabaseAuthSession(request); + if (authSession.data.session) { + return response; + } + + return NextResponse.redirect(new URL('/sign-in', request.url)); +} + +export const config = { + matcher: ['/((?!_next/static|_next/image|favicon.ico|logo.png|logo.svg).*)'], +}; diff --git a/package.json b/package.json index e6eef42..e39013d 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,16 @@ "prepare": "is-ci || husky install", "start": "next start", "storybook": "storybook dev -p 6006", + "supabase:login": "supabase login", + "supabase:types": "supabase gen types typescript --project-id zcetythxtznisfgaakyk > types/database.ts", "typecheck": "tsc --noEmit" }, "prettier": "@vercel/style-guide/prettier", "dependencies": { "@radix-ui/react-icons": "^1.3.0", "@radix-ui/themes": "^2.0.2", + "@supabase/ssr": "^0.0.10", + "@supabase/supabase-js": "^2.39.0", "next": "14.0.4", "next-themes": "1.0.0-beta.0", "react": "^18", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index caa3180..4d10da5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,12 @@ dependencies: '@radix-ui/themes': specifier: ^2.0.2 version: 2.0.2(@types/react-dom@18.2.17)(@types/react@18.2.43)(react-dom@18.2.0)(react@18.2.0) + '@supabase/ssr': + specifier: ^0.0.10 + version: 0.0.10(@supabase/supabase-js@2.39.0) + '@supabase/supabase-js': + specifier: ^2.39.0 + version: 2.39.0 next: specifier: 14.0.4 version: 14.0.4(@babel/core@7.23.5)(react-dom@18.2.0)(react@18.2.0) @@ -4439,6 +4445,73 @@ packages: file-system-cache: 2.3.0 dev: true + /@supabase/functions-js@2.1.5: + resolution: {integrity: sha512-BNzC5XhCzzCaggJ8s53DP+WeHHGT/NfTsx2wUSSGKR2/ikLFQTBCDzMvGz/PxYMqRko/LwncQtKXGOYp1PkPaw==} + dependencies: + '@supabase/node-fetch': 2.6.15 + dev: false + + /@supabase/gotrue-js@2.60.1: + resolution: {integrity: sha512-dM28NhyPS5NLWpJbVokxGbuEMmMK2K+EBXYlNU2NEYzp1BrkdxetNh8ucslMbKauJ93XAEhbMCQHSO9fZ2E+DQ==} + dependencies: + '@supabase/node-fetch': 2.6.15 + dev: false + + /@supabase/node-fetch@2.6.15: + resolution: {integrity: sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==} + engines: {node: 4.x || >=6.0.0} + dependencies: + whatwg-url: 5.0.0 + dev: false + + /@supabase/postgrest-js@1.9.0: + resolution: {integrity: sha512-axP6cU69jDrLbfihJKQ6vU27tklD0gzb9idkMN363MtTXeJVt5DQNT3JnJ58JVNBdL74hgm26rAsFNvHk+tnSw==} + dependencies: + '@supabase/node-fetch': 2.6.15 + dev: false + + /@supabase/realtime-js@2.9.0: + resolution: {integrity: sha512-e/SI+/eqFJorAKAgVAwKQ9hSDQSBp86Yh7XbQmfJJ90LEfpM52HlTfJt/03lcepRu6BmH5h1uYn1b4zta7ghdw==} + dependencies: + '@supabase/node-fetch': 2.6.15 + '@types/phoenix': 1.6.4 + '@types/websocket': 1.0.10 + ws: 8.14.2 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + + /@supabase/ssr@0.0.10(@supabase/supabase-js@2.39.0): + resolution: {integrity: sha512-eVs7+bNlff8Fd79x8K3Jbfpmf8P8QRA1Z6rUDN+fi4ReWvRBZyWOFfR6eqlsX6vTjvGgTiEqujFSkv2PYW5kbQ==} + peerDependencies: + '@supabase/supabase-js': ^2.33.1 + dependencies: + '@supabase/supabase-js': 2.39.0 + cookie: 0.5.0 + ramda: 0.29.0 + dev: false + + /@supabase/storage-js@2.5.5: + resolution: {integrity: sha512-OpLoDRjFwClwc2cjTJZG8XviTiQH4Ik8sCiMK5v7et0MDu2QlXjCAW3ljxJB5+z/KazdMOTnySi+hysxWUPu3w==} + dependencies: + '@supabase/node-fetch': 2.6.15 + dev: false + + /@supabase/supabase-js@2.39.0: + resolution: {integrity: sha512-cYfnwWRW5rYBbPT/BNIejtRT9ULdD9PnIExQV28PZpqcqm3PLwS4f3pY7WGB01Da63VYdvktZPKuYvreqsj/Zg==} + dependencies: + '@supabase/functions-js': 2.1.5 + '@supabase/gotrue-js': 2.60.1 + '@supabase/node-fetch': 2.6.15 + '@supabase/postgrest-js': 1.9.0 + '@supabase/realtime-js': 2.9.0 + '@supabase/storage-js': 2.5.5 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + /@swc/core-darwin-arm64@1.3.100: resolution: {integrity: sha512-XVWFsKe6ei+SsDbwmsuRkYck1SXRpO60Hioa4hoLwR8fxbA9eVp6enZtMxzVVMBi8ej5seZ4HZQeAWepbukiBw==} engines: {node: '>=10'} @@ -4811,7 +4884,6 @@ packages: resolution: {integrity: sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==} dependencies: undici-types: 5.26.5 - dev: true /@types/normalize-package-data@2.4.4: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -4821,6 +4893,10 @@ packages: resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} dev: true + /@types/phoenix@1.6.4: + resolution: {integrity: sha512-B34A7uot1Cv0XtaHRYDATltAdKx0BvVKNgYNqE4WjtPUa4VQJM7kxeXcVKaH+KS+kCmZ+6w+QaUdcljiheiBJA==} + dev: false + /@types/pretty-hrtime@1.0.3: resolution: {integrity: sha512-nj39q0wAIdhwn7DGUyT9irmsKK1tV0bd5WFEhgpqNTMFZ8cE+jieuTphCW0tfdm47S2zVT5mr09B28b1chmQMA==} dev: true @@ -4882,6 +4958,12 @@ packages: resolution: {integrity: sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==} dev: true + /@types/websocket@1.0.10: + resolution: {integrity: sha512-svjGZvPB7EzuYS94cI7a+qhwgGU1y89wUgjT6E2wVUfmAGIvRfT7obBvRtnhXCSsoMdlG4gBFGE7MfkIXZLoww==} + dependencies: + '@types/node': 20.10.4 + dev: false + /@types/yargs-parser@21.0.3: resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} dev: true @@ -6401,7 +6483,6 @@ packages: /cookie@0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} - dev: true /core-js-compat@3.34.0: resolution: {integrity: sha512-4ZIyeNbW/Cn1wkMMDy+mvrRUxrwFNjKwbhCfQpDd+eLgYipDqp8oGFGtLmhh18EDPKA0g3VUBYOxQGGwvWLVpA==} @@ -10662,7 +10743,6 @@ packages: /ramda@0.29.0: resolution: {integrity: sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==} - dev: true /randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -11987,7 +12067,6 @@ packages: /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - dev: true /trim-newlines@3.0.1: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} @@ -12196,7 +12275,6 @@ packages: /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - dev: true /unicode-canonical-property-names-ecmascript@2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} @@ -12399,7 +12477,6 @@ packages: /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - dev: true /webpack-dev-middleware@6.1.1(webpack@5.89.0): resolution: {integrity: sha512-y51HrHaFeeWir0YO4f0g+9GwZawuigzcAdRNon6jErXy/SqV/+O6eaVAzDqE6t3e3NpGeR5CS+cCDaTC+V3yEQ==} @@ -12484,7 +12561,6 @@ packages: dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 - dev: true /which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -12618,7 +12694,6 @@ packages: optional: true utf-8-validate: optional: true - dev: true /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} diff --git a/types/database.ts b/types/database.ts new file mode 100644 index 0000000..07dbe5a --- /dev/null +++ b/types/database.ts @@ -0,0 +1,348 @@ +export type Json = + | { [key: string]: Json | undefined } + | Json[] + | boolean + | null + | number + | string; + +export type Database = { + public: { + CompositeTypes: { + [_ in never]: never; + }; + Enums: { + [_ in never]: never; + }; + Functions: { + [_ in never]: never; + }; + Tables: { + episode: { + Insert: { + audio_url: string; + created_at?: string; + description?: null | string; + duration: number; + episode_number: number; + id?: number; + images?: null | string[]; + podcast_index_guid: string; + show: number; + size?: null | number; + title: string; + }; + Relationships: [ + { + columns: ['show']; + foreignKeyName: 'episode_show_fkey'; + isOneToOne: false; + referencedColumns: ['id']; + referencedRelation: 'show'; + }, + ]; + Row: { + audio_url: string; + created_at: string; + description: null | string; + duration: number; + episode_number: number; + id: number; + images: null | string[]; + podcast_index_guid: string; + show: number; + size: null | number; + title: string; + }; + Update: { + audio_url?: string; + created_at?: string; + description?: null | string; + duration?: number; + episode_number?: number; + id?: number; + images?: null | string[]; + podcast_index_guid?: string; + show?: number; + size?: null | number; + title?: string; + }; + }; + episode_content: { + Insert: { + created_at?: string; + episode: number; + id?: number; + text_summary?: null | string; + transcription?: null | string; + user: number; + }; + Relationships: [ + { + columns: ['episode']; + foreignKeyName: 'episode_content_episode_fkey'; + isOneToOne: false; + referencedColumns: ['id']; + referencedRelation: 'episode'; + }, + { + columns: ['user']; + foreignKeyName: 'episode_content_user_fkey'; + isOneToOne: false; + referencedColumns: ['id']; + referencedRelation: 'user'; + }, + ]; + Row: { + created_at: string; + episode: number; + id: number; + text_summary: null | string; + transcription: null | string; + user: number; + }; + Update: { + created_at?: string; + episode?: number; + id?: number; + text_summary?: null | string; + transcription?: null | string; + user?: number; + }; + }; + episode_progress: { + Insert: { + at: number; + created_at?: string; + episode: number; + finished?: boolean; + id?: number; + user: number; + }; + Relationships: [ + { + columns: ['episode']; + foreignKeyName: 'episode_progress_episode_fkey'; + isOneToOne: false; + referencedColumns: ['id']; + referencedRelation: 'episode'; + }, + { + columns: ['user']; + foreignKeyName: 'episode_progress_user_fkey'; + isOneToOne: false; + referencedColumns: ['id']; + referencedRelation: 'user'; + }, + ]; + Row: { + at: number; + created_at: string; + episode: number; + finished: boolean; + id: number; + user: number; + }; + Update: { + at?: number; + created_at?: string; + episode?: number; + finished?: boolean; + id?: number; + user?: number; + }; + }; + show: { + Insert: { + created_at?: string; + description?: null | string; + id?: number; + images?: null | string[]; + language?: null | string; + podcast_index_guid: string; + publisher: string; + spotify_id: string; + title: string; + total_episode?: null | number; + }; + Relationships: []; + Row: { + created_at: string; + description: null | string; + id: number; + images: null | string[]; + language: null | string; + podcast_index_guid: string; + publisher: string; + spotify_id: string; + title: string; + total_episode: null | number; + }; + Update: { + created_at?: string; + description?: null | string; + id?: number; + images?: null | string[]; + language?: null | string; + podcast_index_guid?: string; + publisher?: string; + spotify_id?: string; + title?: string; + total_episode?: null | number; + }; + }; + user: { + Insert: { + ai_credit?: null | number; + avatar?: null | string; + created_at?: string; + email?: null | string; + id?: number; + password?: null | string; + provider_token?: null | string; + username?: null | string; + }; + Relationships: []; + Row: { + ai_credit: null | number; + avatar: null | string; + created_at: string; + email: null | string; + id: number; + password: null | string; + provider_token: null | string; + username: null | string; + }; + Update: { + ai_credit?: null | number; + avatar?: null | string; + created_at?: string; + email?: null | string; + id?: number; + password?: null | string; + provider_token?: null | string; + username?: null | string; + }; + }; + user_show_relation: { + Insert: { + created_at?: string; + id?: number; + show: number; + user: number; + }; + Relationships: [ + { + columns: ['show']; + foreignKeyName: 'user_show_relation_show_fkey'; + isOneToOne: false; + referencedColumns: ['id']; + referencedRelation: 'show'; + }, + { + columns: ['user']; + foreignKeyName: 'user_show_relation_user_fkey'; + isOneToOne: false; + referencedColumns: ['id']; + referencedRelation: 'user'; + }, + ]; + Row: { + created_at: string; + id: number; + show: number; + user: number; + }; + Update: { + created_at?: string; + id?: number; + show?: number; + user?: number; + }; + }; + }; + Views: { + [_ in never]: never; + }; + }; +}; + +export type Tables< + PublicTableNameOrOptions extends + | { schema: keyof Database } + | keyof (Database['public']['Tables'] & Database['public']['Views']), + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof (Database[PublicTableNameOrOptions['schema']]['Tables'] & + Database[PublicTableNameOrOptions['schema']]['Views']) + : never = never, +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? (Database[PublicTableNameOrOptions['schema']]['Tables'] & + Database[PublicTableNameOrOptions['schema']]['Views'])[TableName] extends { + Row: infer R; + } + ? R + : never + : PublicTableNameOrOptions extends keyof (Database['public']['Tables'] & + Database['public']['Views']) + ? (Database['public']['Tables'] & + Database['public']['Views'])[PublicTableNameOrOptions] extends { + Row: infer R; + } + ? R + : never + : never; + +export type TablesInsert< + PublicTableNameOrOptions extends + | { schema: keyof Database } + | keyof Database['public']['Tables'], + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicTableNameOrOptions['schema']]['Tables'] + : never = never, +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends { + Insert: infer I; + } + ? I + : never + : PublicTableNameOrOptions extends keyof Database['public']['Tables'] + ? Database['public']['Tables'][PublicTableNameOrOptions] extends { + Insert: infer I; + } + ? I + : never + : never; + +export type TablesUpdate< + PublicTableNameOrOptions extends + | { schema: keyof Database } + | keyof Database['public']['Tables'], + TableName extends PublicTableNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicTableNameOrOptions['schema']]['Tables'] + : never = never, +> = PublicTableNameOrOptions extends { schema: keyof Database } + ? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends { + Update: infer U; + } + ? U + : never + : PublicTableNameOrOptions extends keyof Database['public']['Tables'] + ? Database['public']['Tables'][PublicTableNameOrOptions] extends { + Update: infer U; + } + ? U + : never + : never; + +export type Enums< + PublicEnumNameOrOptions extends + | { schema: keyof Database } + | keyof Database['public']['Enums'], + EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database } + ? keyof Database[PublicEnumNameOrOptions['schema']]['Enums'] + : never = never, +> = PublicEnumNameOrOptions extends { schema: keyof Database } + ? Database[PublicEnumNameOrOptions['schema']]['Enums'][EnumName] + : PublicEnumNameOrOptions extends keyof Database['public']['Enums'] + ? Database['public']['Enums'][PublicEnumNameOrOptions] + : never;