diff --git a/.github/workflows/avoid-typos.yml b/.github/workflows/avoid-typos.yml index 1cbe64238ac..68a17b5ba32 100644 --- a/.github/workflows/avoid-typos.yml +++ b/.github/workflows/avoid-typos.yml @@ -17,4 +17,8 @@ jobs: locale: "US" reporter: github-pr-review level: error - exclude: "*.css" + exclude: | + "*.css" + "**/package.json" + "**/package-lock.json" + ".git/*" diff --git a/.gitignore b/.gitignore index 8e245433c33..c78e6a2eb47 100644 --- a/.gitignore +++ b/.gitignore @@ -122,4 +122,5 @@ typings/ apps/new-docs/* -gcloud.json \ No newline at end of file +# For self-hosted logs: https://github.com/supabase/supabase/blob/86e3ab20abfdb9c3e666334d3d2f8efeef9ccf2c/docker/docker-compose-logging.yml#L101 +gcloud.json diff --git a/README.md b/README.md index fcf1cfe5aba..f3506927e0a 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ - [x] Authentication and Authorization. [Docs](https://supabase.com/docs/guides/auth) - [x] Auto-generated APIs. - [x] REST. [Docs](https://supabase.com/docs/guides/database/api#rest-api) + - [x] GraphQL. [Docs](https://supabase.com/docs/guides/database/api#graphql-api) - [x] Realtime subscriptions. [Docs](https://supabase.com/docs/guides/database/api#realtime-api) - - [x] GraphQL (Beta). [Docs](https://supabase.com/docs/guides/database/api#graphql-api) - [x] Functions. - [x] Database Functions. [Docs](https://supabase.com/docs/guides/database/functions) - [x] Edge Functions [Docs](https://supabase.com/docs/guides/functions) @@ -63,6 +63,7 @@ You can also [self-host](https://supabase.com/docs/guides/hosting/overview) and - [PostgreSQL](https://www.postgresql.org/) is an object-relational database system with over 30 years of active development that has earned it a strong reputation for reliability, feature robustness, and performance. - [Realtime](https://github.com/supabase/realtime) is an Elixir server that allows you to listen to PostgreSQL inserts, updates, and deletes using websockets. Realtime polls Postgres' built-in replication functionality for database changes, converts changes to JSON, then broadcasts the JSON over websockets to authorized clients. - [PostgREST](http://postgrest.org/) is a web server that turns your PostgreSQL database directly into a RESTful API +- [pg_graphql](http://github.com/supabase/pg_graphql/) a PostgreSQL extension that exposes a GraphQL API - [Storage](https://github.com/supabase/storage-api) provides a RESTful interface for managing Files stored in S3, using Postgres to manage permissions. - [postgres-meta](https://github.com/supabase/postgres-meta) is a RESTful API for managing your Postgres, allowing you to fetch tables, add roles, and run queries, etc. - [GoTrue](https://github.com/netlify/gotrue) is an SWT based API for managing users and issuing SWT tokens. diff --git a/apps/docs/components/Favicons.tsx b/apps/docs/components/Favicons.tsx index 5c65346f0fa..1e9ddafffe1 100644 --- a/apps/docs/components/Favicons.tsx +++ b/apps/docs/components/Favicons.tsx @@ -5,32 +5,46 @@ const Favicons = () => { const { basePath } = useRouter() return ( - - - - - {/* */} - + {/* prettier-ignore */} + + {/* prettier-ignore */} + + {/* prettier-ignore */} + + {/* prettier-ignore */} + + {/* prettier-ignore */} + + {/* prettier-ignore */} + + {/* prettier-ignore */} + + {/* prettier-ignore */} + + {/* prettier-ignore */} + - + + + + + - + - + {/* misc */} + + + ) } diff --git a/apps/docs/components/Navigation/NavBar.tsx b/apps/docs/components/Navigation/NavBar.tsx deleted file mode 100644 index eb7ee10434f..00000000000 --- a/apps/docs/components/Navigation/NavBar.tsx +++ /dev/null @@ -1,207 +0,0 @@ -import { useTheme } from 'common/Providers' -import Image from 'next/image' -import Link from 'next/link' -import { useRouter } from 'next/router' -import { FC, useEffect, useState } from 'react' -import { IconCommand, IconMenu, IconMoon, IconSearch, IconSun, Listbox, SearchButton } from 'ui' -import { REFERENCES } from './Navigation.constants' - -import { getPageType } from '~/lib/helpers' - -const NavBar: FC = () => { - const { isDarkMode, toggleTheme } = useTheme() - const [mounted, setMounted] = useState(false) - const [mobileMenuOpen, setMobileMenuOpen] = useState(false) - - const { asPath, push } = useRouter() - const pathSegments = asPath.split('/') - - const library = pathSegments.length >= 3 ? pathSegments[2] : undefined - const libraryMeta = REFERENCES?.[library] ?? undefined - const versions = libraryMeta?.versions ?? [] - - const version = versions.includes(pathSegments[pathSegments.indexOf(library) + 1]) - ? pathSegments[pathSegments.indexOf(library) + 1] - : versions[0] - - const pageType = getPageType(asPath) - - useEffect(() => { - setMounted(true) - }, [isDarkMode]) - - const pageLinks = [ - { text: 'Guides', key: 'docs', link: '/' }, - { text: 'Reference', key: 'reference', link: '/reference' }, - ] - - const onSelectVersion = (version: string) => { - // [Joshen] Ideally we use but this works for now - if (!library) return - if (version === versions[0]) { - push(`/reference/${library}`) - } else { - push(`/reference/${library}/${version}`) - } - } - - // [Joshen] Kaizen: Use UI library's SidePanel for this - const toggleMobileMenu = () => { - setMobileMenuOpen(!mobileMenuOpen) - const sidebar = document.querySelector('.sidebar-menu-container') - const contentPane = document.querySelector('.main-content-pane') - - sidebar.classList.toggle('hidden') - contentPane.classList.toggle('hidden') - } - - return ( - - ) -} -export default NavBar diff --git a/apps/docs/components/Navigation/Navigation.constants.ts b/apps/docs/components/Navigation/Navigation.constants.ts deleted file mode 100644 index f3b9ee85430..00000000000 --- a/apps/docs/components/Navigation/Navigation.constants.ts +++ /dev/null @@ -1,431 +0,0 @@ -// [Terry] Also need to look at getPageType() in ./lib/helpers -// to set a menu to a page -import SupabaseJsV1Nav from 'data/nav/supabase-js/v1' -import SupabaseJsV2Nav from 'data/nav/supabase-js/v2' -import SupabaseDartV0Nav from 'data/nav/supabase-dart/v0' -import SupabaseDartV1Nav from 'data/nav/supabase-dart/v1' -import SupabaseCLINav from 'data/nav/supabase-cli' -import SupabaseAPINav from 'data/nav/supabase-api' -import AuthServerNav from 'data/nav/auth-server' -import RealtimeServerNav from 'data/nav/realtime-server' -import StorageServerNav from 'data/nav/storage-server' - -import { NavMenu, References } from './Navigation.types' - -export const REFERENCES: References = { - javascript: { - name: 'supabase-js', - library: 'supabase-js', - versions: ['v2', 'v1'], - icon: '/docs/img/icons/javascript-icon.svg', - }, - dart: { - name: 'Flutter', - library: 'supabase-dart', - versions: ['v1', 'v0'], - icon: '/docs/img/icons/flutter-icon.svg', - }, - cli: { - name: 'CLI', - library: undefined, - versions: [], - icon: '/docs/img/icons/cli-icon.svg', - }, - api: { - name: 'API', - library: undefined, - versions: [], - icon: '/docs/img/icons/api-icon.svg', - }, -} - -export const menuItems: NavMenu = { - docs: [ - { - label: 'Overview', - items: [ - { name: 'Introduction', url: '/', items: [] }, - { name: 'Features', url: '/features', items: [] }, - { name: 'Architecture', url: '/architecture', items: [] }, - ], - }, - { - label: 'Quickstarts', - items: [ - { name: 'Angular', url: '/guides/with-angular', items: [] }, - { name: 'Expo', url: '/guides/with-expo', items: [] }, - { name: 'Flutter', url: '/guides/with-flutter', items: [] }, - { name: 'Ionic Angular', url: '/guides/with-ionic-angular', items: [] }, - { name: 'Ionic React', url: '/guides/with-ionic-react', items: [] }, - { name: 'Ionic Vue', url: '/guides/with-ionic-vue', items: [] }, - { name: 'Next.js', url: '/guides/with-nextjs', items: [] }, - { name: 'Nuxt 3', url: '/guides/with-nuxt-3', items: [] }, - { name: 'React', url: '/guides/with-react', items: [] }, - { name: 'RedwoodJS', url: '/guides/with-redwoodjs', items: [] }, - { name: 'SolidJS', url: '/guides/with-solidjs', items: [] }, - { name: 'Svelte', url: '/guides/with-svelte', items: [] }, - { name: 'SvelteKit', url: '/guides/with-sveltekit', items: [] }, - { name: 'Vue 3', url: '/guides/with-vue-3', items: [] }, - ], - }, - { - label: 'CLI', - items: [ - { name: 'Overview', url: '/guides/cli', items: [] }, - { name: 'Local Development', url: '/guides/cli/local-development', items: [] }, - { name: 'Managing Environments', url: '/guides/cli/managing-environments', items: [] }, - ], - }, - { - label: 'Auth', - items: [ - { name: 'Overview', url: '/guides/auth', items: [] }, - { name: 'Quickstarts', url: '/guides/auth/quickstarts/react', items: [] }, - { - name: 'Authentication', - url: undefined, - items: [ - { name: 'Login with Email', url: '/guides/auth/auth-email', items: [] }, - { name: 'Login with Magic Link', url: '/guides/auth/auth-magic-link', items: [] }, - { name: 'Login with Apple', url: '/guides/auth/auth-apple', items: [] }, - { name: 'Login with Azure', url: '/guides/auth/auth-azure', items: [] }, - { name: 'Login with Bitbucket', url: '/guides/auth/auth-bitbucket', items: [] }, - { name: 'Login with Discord', url: '/guides/auth/auth-discord', items: [] }, - { name: 'Login with Facebook', url: '/guides/auth/auth-facebook', items: [] }, - { name: 'Login with Github', url: '/guides/auth/auth-github', items: [] }, - { name: 'Login with Gitlab', url: '/guides/auth/auth-gitlab', items: [] }, - { name: 'Login with Google', url: '/guides/auth/auth-google', items: [] }, - { name: 'Login with Keycloak', url: '/guides/auth/auth-keycloak', items: [] }, - { name: 'Login with LinkedIn', url: '/guides/auth/auth-linkedin', items: [] }, - { name: 'Login with Notion', url: '/guides/auth/auth-notion', items: [] }, - { name: 'Login with Slack', url: '/guides/auth/auth-slack', items: [] }, - { name: 'Login with Spotify', url: '/guides/auth/auth-spotify', items: [] }, - { name: 'Login with Twitch', url: '/guides/auth/auth-twitch', items: [] }, - { name: 'Login with Twitter', url: '/guides/auth/auth-twitter', items: [] }, - { name: 'Login with WorkOS', url: '/guides/auth/auth-workos', items: [] }, - { name: 'Login with Zoom', url: '/guides/auth/auth-zoom', items: [] }, - { name: 'Phone Auth with Twilio', url: '/guides/auth/auth-twilio', items: [] }, - { name: 'Phone Auth with Vonage', url: '/guides/auth/auth-vonage', items: [] }, - { - name: 'Phone Auth with MessageBird', - url: '/guides/auth/auth-messagebird', - items: [], - }, - ], - }, - { - name: 'Authorization', - url: undefined, - items: [ - { name: 'Row Level Security', url: '/guides/auth/row-level-security', items: [] }, - { name: 'Managing User Data', url: '/guides/auth/managing-user-data', items: [] }, - { name: 'Enable Captcha Protection', url: '/guides/auth/auth-captcha', items: [] }, - { name: 'Server-side Rendering', url: '/guides/auth/server-side-rendering', items: [] }, - { name: 'Multi-Factor Authentication', url: '/guides/auth/auth-mfa', items: [] }, - ], - }, - { - name: 'Auth Helpers', - url: undefined, - items: [ - { name: 'Overview', url: '/guides/auth/auth-helpers', items: [] }, - { name: 'Auth UI', url: '/guides/auth/auth-helpers/auth-ui', items: [] }, - { name: 'Next.js (pages)', url: '/guides/auth/auth-helpers/nextjs', items: [] }, - { - name: 'Next.js (app)', - url: '/guides/auth/auth-helpers/nextjs-server-components', - items: [], - }, - { name: 'SvelteKit', url: '/guides/auth/auth-helpers/sveltekit', items: [] }, - { name: 'Remix', url: '/guides/auth/auth-helpers/remix', items: [] }, - ], - }, - { - name: 'Deep Dive', - url: undefined, - items: [ - { - name: 'Part One: JWTs', - url: '/learn/auth-deep-dive/auth-deep-dive-jwts', - items: [], - }, - { - name: 'Part Two: Row Level Security', - url: '/learn/auth-deep-dive/auth-row-level-security', - items: [], - }, - { name: 'Part Three: Policies', url: '/learn/auth-deep-dive/auth-policies', items: [] }, - { name: 'Part Four: GoTrue', url: '/learn/auth-deep-dive/auth-gotrue', items: [] }, - { - name: 'Part Five: Google OAuth', - url: '/learn/auth-deep-dive/auth-google-oauth', - items: [], - }, - ], - }, - ], - }, - { - label: 'Database', - items: [ - { name: 'Overview', url: '/guides/database', items: [] }, - { name: 'Database Connections', url: '/guides/database/connecting-to-postgres', items: [] }, - { name: 'Tables and Data', url: '/guides/database/tables', items: [] }, - { name: 'Database Functions', url: '/guides/database/functions', items: [] }, - { name: 'Database Webhooks', url: '/guides/database/webhooks', items: [] }, - { name: 'Full Text Search', url: '/guides/database/full-text-search', items: [] }, - { name: 'Database Testing', url: '/guides/database/testing', items: [] }, - { - name: 'Extensions', - url: undefined, - items: [ - { name: 'Overview', url: '/guides/database/extensions', items: [] }, - { name: 'http: RESTful Client', url: '/guides/database/extensions/http', items: [] }, - { - name: 'pg_cron: Job Scheduling', - url: '/guides/database/extensions/pgcron', - items: [], - }, - { - name: 'pg_net: Async Networking', - url: '/guides/database/extensions/pgnet', - items: [], - }, - { name: 'pgTAP: Unit Testing', url: '/guides/database/extensions/pgtap', items: [] }, - { - name: 'plv8: Javascript Language', - url: '/guides/database/extensions/plv8', - items: [], - }, - { - name: 'PostGIS: Geo queries', - url: '/guides/database/extensions/postgis', - items: [], - }, - { - name: 'uuid-ossp: Unique Identifiers', - url: '/guides/database/extensions/uuid-ossp', - items: [], - }, - ], - }, - { - name: 'Configuration', - url: undefined, - items: [ - { name: 'Timeouts', url: '/guides/database/timeouts', items: [] }, - { name: 'Replication', url: '/guides/database/replication', items: [] }, - { name: 'Passwords', url: '/guides/database/managing-passwords', items: [] }, - { name: 'Timezones', url: '/guides/database/managing-timezones', items: [] }, - ], - }, - ], - }, - { - label: 'Serverless APIs', - items: [ - { name: 'Overview', url: '/guides/api', items: [] }, - { name: 'Generating Types', url: '/guides/api/generating-types', items: [] }, - ], - }, - { - label: 'Edge Functions', - items: [ - { name: 'Overview', url: '/guides/functions', items: [] }, - { name: 'Quickstart', url: '/guides/functions/quickstart', items: [] }, - { name: 'Auth', url: '/guides/functions/auth', items: [] }, - { name: 'Best Practices', url: '/guides/functions/best-practices', items: [] }, - { name: 'CI/CD Workflow', url: '/guides/functions/cicd-workflow', items: [] }, - ], - }, - { - label: 'Realtime', - items: [ - { - name: 'Overview', - url: '/guides/realtime', - items: [], - }, - { - name: 'Quickstart', - url: '/guides/realtime/quickstart', - items: [], - }, - { - name: 'Features', - url: undefined, - items: [ - { name: 'Channels', url: '/guides/realtime/channels', items: [] }, - { - name: 'Extensions', - url: '/guides/realtime/extensions', - items: [ - { name: 'Broadcast', url: '/guides/realtime/broadcast', items: [] }, - { name: 'Presence', url: '/guides/realtime/presence', items: [] }, - { name: 'Postgres Changes', url: '/guides/realtime/postgres-changes', items: [] }, - ], - }, - ], - }, - { - name: 'Guides', - url: undefined, - items: [ - { - name: 'Subscribing to Database Changes', - url: '/guides/realtime/subscribing-to-database-changes', - items: [], - }, - { - name: 'Bring Your Own Database', - url: '/guides/realtime/bring-your-own-database', - items: [], - }, - { - name: 'Using Realtime with Next.js', - url: '/guides/realtime/realtime-with-nextjs', - items: [], - }, - ], - }, - { - name: 'Deep dive', - url: undefined, - items: [ - { name: 'Rate Limits', url: '/guides/realtime/rate-limits', items: [] }, - { name: 'Architecture', url: '/guides/realtime/architecture', items: [] }, - { name: 'Protocol', url: '/guides/realtime/protocol', items: [] }, - ], - }, - ], - }, - { - label: 'Storage', - items: [ - { name: 'Overview', url: '/guides/storage', items: [] }, - { name: 'Quickstart', url: '/guides/storage/quickstart', items: [] }, - { name: 'Access Control', url: '/guides/storage/access-control', items: [] }, - { name: 'CDN', url: '/guides/storage/cdn', items: [] }, - { name: 'Image Transformations', url: '/guides/storage/image-transformations', items: [] }, - ], - }, - { - label: 'Platform', - items: [ - { name: 'Overview', url: '/guides/platform', items: [] }, - { name: 'Compute Add-ons', url: '/guides/platform/compute-add-ons', items: [] }, - { name: 'Custom Domains', url: '/guides/platform/custom-domains', items: [] }, - { name: 'Database Usage', url: '/guides/platform/database-usage', items: [] }, - { name: 'HTTP Status Codes', url: '/guides/platform/http-status-codes', items: [] }, - { name: 'Logging', url: '/guides/platform/logs', items: [] }, - { name: 'Metrics', url: '/guides/platform/metrics', items: [] }, - { - name: 'Migrating and upgrading', - url: '/guides/platform/migrating-and-upgrading-projects', - items: [], - }, - { name: 'Network Restrictions', url: '/guides/platform/network-restrictions', items: [] }, - { name: 'Performance Tuning', url: '/guides/platform/performance', items: [] }, - { name: 'Permissions', url: '/guides/platform/permissions', items: [] }, - { name: 'Production Readiness', url: '/guides/platform/going-into-prod', items: [] }, - { name: 'SSL Enforcement', url: '/guides/platform/ssl-enforcement', items: [] }, - { name: 'Troubleshooting', url: '/guides/platform/troubleshooting', items: [] }, - ], - }, - { - label: 'Self Hosting', - items: [ - { name: 'Overview', url: '/guides/hosting/overview', items: [] }, - { name: 'Docker', url: '/guides/hosting/docker', items: [] }, - ], - }, - { - label: 'Migrate to Supabase', - items: [ - { name: 'Firebase Auth', url: '/guides/migrations/firebase-auth', items: [] }, - { name: 'Firestore Data', url: '/guides/migrations/firestore-data', items: [] }, - { name: 'Firebase Storage', url: '/guides/migrations/firebase-storage', items: [] }, - { name: 'Heroku', url: '/guides/migrations/heroku', items: [] }, - ], - }, - { - label: 'Integrations', - items: [ - { name: 'Supabase Marketplace', url: '/guides/integrations/integrations', items: [] }, - { - name: 'Auth', - url: undefined, - items: [ - { name: 'Auth0', url: '/guides/integrations/auth0', items: [] }, - { name: 'Authsignal', url: '/guides/integrations/authsignal', items: [] }, - { name: 'Clerk', url: '/guides/integrations/clerk', items: [] }, - { name: 'keyri', url: '/guides/integrations/keyri', items: [] }, - { name: 'Picket', url: '/guides/integrations/picket', items: [] }, - { name: 'Stytch', url: '/guides/integrations/stytch', items: [] }, - { name: 'SuperTokens', url: '/guides/integrations/supertokens', items: [] }, - ], - }, - { - name: 'Caching / Offline-first', - url: undefined, - items: [{ name: 'Polyscale', url: '/guides/integrations/polyscale', items: [] }], - }, - { - name: 'Developer Tools', - url: undefined, - items: [ - { name: 'Estuary', url: '/guides/integrations/estuary', items: [] }, - { name: 'pgMustard', url: '/guides/integrations/pgmustard', items: [] }, - { name: 'Prisma', url: '/guides/integrations/prisma', items: [] }, - { name: 'Sequin', url: '/guides/integrations/sequin', items: [] }, - { name: 'Snaplet', url: '/guides/integrations/snaplet', items: [] }, - { name: 'Vercel', url: '/guides/integrations/vercel', items: [] }, - { name: 'Zuplo', url: '/guides/integrations/zuplo', items: [] }, - ], - }, - { - name: 'Low-code', - url: undefined, - items: [ - { name: 'Appsmith', url: '/guides/integrations/appsmith', items: [] }, - { name: 'DhiWise', url: '/guides/integrations/dhiwise', items: [] }, - { name: 'Directus', url: '/guides/integrations/directus', items: [] }, - { name: 'Draftbit', url: '/guides/integrations/draftbit', items: [] }, - { name: 'Plasmic', url: '/guides/integrations/plasmic', items: [] }, - { name: 'WeWeb', url: '/guides/integrations/weweb', items: [] }, - { name: 'ILLA', url: '/guides/integrations/illa', items: [] }, - ], - }, - ], - }, - ], - reference: [ - { - label: 'Official', - items: [ - { name: 'Reference Documentation', url: '/reference', items: [] }, - { name: 'Supabase JavaScript Library', url: '/reference/javascript', items: [] }, - { name: 'Supabase Flutter Library', url: '/reference/dart', items: [] }, - { name: 'Supabase CLI', url: '/reference/cli', items: [] }, - { name: 'Management API', url: '/reference/api', items: [] }, - ], - }, - { - label: 'Self-hosting', - items: [ - { name: 'Auth Server', url: '/reference/auth', items: [] }, - { name: 'Realtime Server', url: '/reference/realtime', items: [] }, - { name: 'Storage Server', url: '/reference/storage', items: [] }, - ], - }, - ], - 'reference/javascript': SupabaseJsV2Nav, - 'reference/javascript/v1': SupabaseJsV1Nav, - 'reference/dart': SupabaseDartV1Nav, - 'reference/dart/v0': SupabaseDartV0Nav, - 'reference/cli': SupabaseCLINav, - 'reference/api': SupabaseAPINav, - 'reference/auth': AuthServerNav, - 'reference/realtime': RealtimeServerNav, - 'reference/storage': StorageServerNav, -} diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index a2f78fc985d..c4739ffc3ab 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -1,13 +1,4 @@ -// import SupabaseJsV1Nav from 'data/nav/supabase-js/v1' -// import SupabaseJsV2Nav from 'data/nav/supabase-js/v2' -// import SupabaseDartV0Nav from 'data/nav/supabase-dart/v0' -// import SupabaseDartV1Nav from 'data/nav/supabase-dart/v1' -// import SupabaseCLINav from 'data/nav/supabase-cli' -// import SupabaseAPINav from 'data/nav/supabase-api' -// import AuthServerNav from 'data/nav/auth-server' -// import StorageServerNav from 'data/nav/storage-server' - -import { NavMenu, References } from '../Navigation.types' +import { References } from '../Navigation.types' export const REFERENCES: References = { javascript: { @@ -54,12 +45,13 @@ export const gettingstarted = { items: [ { name: 'Features', url: '/guides/getting-started/features' }, { name: 'Architecture', url: '/guides/getting-started/architecture' }, - { name: 'Local Development', url: '/guides/cli/local-development' }, + // { name: 'Local Development', url: '/guides/cli/local-development' }, // Disabling this until we actually move the doc into this section { name: 'Framework Quickstarts', items: [ { name: 'React', url: '/guides/getting-started/quickstarts/reactjs' }, { name: 'NextJS', url: '/guides/getting-started/quickstarts/nextjs' }, + { name: 'RedwoodJS', url: '/guides/getting-started/quickstarts/redwoodjs' }, { name: 'Flutter', url: '/guides/getting-started/quickstarts/flutter' }, { name: 'SvelteKit', url: '/guides/getting-started/quickstarts/sveltekit' }, { name: 'SolidJS', url: '/guides/getting-started/quickstarts/solidjs' }, @@ -133,6 +125,15 @@ export const gettingstarted = { }, ], }, + { + name: 'AI & ML', + items: [ + { + name: 'Vector Search with OpenAI', + url: '/guides/getting-started/openai/vector-search', + }, + ], + }, ], } @@ -801,6 +802,11 @@ export const resources = { name: 'Render', url: '/guides/resources/migrating-to-supabase/render', }, + { + name: 'Amazon RDS', + url: '/guides/resources/migrating-to-supabase/amazon-rds', + items: [], + }, ], }, ], @@ -853,6 +859,7 @@ export const migrate = { { name: 'Firebase Storage', url: '/guides/migrations/firebase-storage' }, { name: 'Heroku', url: '/guides/migrations/heroku' }, { name: 'Render', url: '/guides/migrations/render' }, + { name: 'Amazon RDS', url: '/guides/migrations/amazon-rds' }, ], } diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuCliList.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuCliList.tsx index e4a7eb5c8f7..cae120747b8 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuCliList.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuCliList.tsx @@ -86,7 +86,7 @@ const NavigationMenuCliList = ({ currentLevel, setLevel, id }) => { return (
= ({ return (
{ const { isDarkMode, toggleTheme } = useTheme() @@ -33,17 +31,10 @@ const TopNavBar: FC = () => { ? pathSegments[pathSegments.indexOf(library) + 1] : versions[0] - const pageType = getPageType(asPath) - useEffect(() => { setMounted(true) }, [isDarkMode]) - const pageLinks = [ - { text: 'Guides', key: 'docs', link: '/' }, - { text: 'Reference', key: 'reference', link: '/reference' }, - ] - const onSelectVersion = (version: string) => { // [Joshen] Ideally we use but this works for now if (!library) return diff --git a/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx b/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx index cb2fb196c8b..6a56ee7d544 100644 --- a/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx @@ -4,9 +4,9 @@ import Link from 'next/link' import { useRouter } from 'next/router' import { FC, useEffect, useState } from 'react' import { Button, IconCommand, IconGitHub, IconMoon, IconSearch, IconSun, SearchButton } from 'ui' -import { REFERENCES } from '~/components/Navigation/Navigation.constants' import { getPageType } from '~/lib/helpers' +import { REFERENCES } from './NavigationMenu.constants' const TopNavBarRef: FC = () => { const { isDarkMode, toggleTheme } = useTheme() diff --git a/apps/docs/components/Navigation/SideBar.tsx b/apps/docs/components/Navigation/SideBar.tsx index d6ffa3473b9..d679b7c7414 100644 --- a/apps/docs/components/Navigation/SideBar.tsx +++ b/apps/docs/components/Navigation/SideBar.tsx @@ -2,7 +2,8 @@ import Link from 'next/link' import Image from 'next/image' import { useRouter } from 'next/router' import { IconChevronRight, IconArrowLeft } from '~/../../packages/ui' -import { REFERENCES } from './Navigation.constants' +import { REFERENCES } from './NavigationMenu/NavigationMenu.constants' + import { NavMenuGroup, NavMenuSection } from './Navigation.types' import * as Accordion from '@radix-ui/react-accordion' diff --git a/apps/docs/components/reference/CLICommandSection.tsx b/apps/docs/components/reference/CLICommandSection.tsx index d20c1db8086..f25d32e74cf 100644 --- a/apps/docs/components/reference/CLICommandSection.tsx +++ b/apps/docs/components/reference/CLICommandSection.tsx @@ -14,6 +14,8 @@ export type Flag = { default_value: string accepted_values: AcceptedValue[] required?: boolean + /** Whether subcommands inherit this flag. */ + inherit?: boolean } export type AcceptedValue = { @@ -46,6 +48,14 @@ export type Command = { const CliCommandSection = (props) => { const command = spec.commands.find((x: any) => x.id === props.funcData.id) + const parentCommand = spec.commands.find( + (x: any) => x.subcommands && x.subcommands.find((y: any) => y === props.funcData.id) + ) + + const commandFlags = [ + ...(parentCommand?.flags?.filter((x: any) => x.inherit) || []), + ...command.flags, + ] return ( { )} - {command.subcommands.length > 0 && ( + {command.subcommands?.length > 0 && (

Available Commands

    @@ -96,11 +106,11 @@ const CliCommandSection = (props) => {
)} - {command.flags.length > 0 && ( + {commandFlags.length > 0 && ( <>

Flags

    - {command.flags.map((flag: Flag) => ( + {commandFlags.map((flag: Flag) => ( <>
  • diff --git a/apps/docs/data/nav/supabase-js/v2.ts b/apps/docs/data/nav/supabase-js/v2.ts index 13925dc1d85..f71236ae3aa 100644 --- a/apps/docs/data/nav/supabase-js/v2.ts +++ b/apps/docs/data/nav/supabase-js/v2.ts @@ -160,6 +160,11 @@ const Nav = [ url: '/reference/javascript/storage-from-createsignedurls', items: [], }, + { + name: 'from.createSignedUploadUrl()', + url: '/reference/javascript/storage-from-createsigneduploadurl', + items: [], + }, { name: 'from.getPublicUrl()', url: '/reference/javascript/storage-from-getpublicurl', diff --git a/apps/docs/docs/ref/python/introduction.mdx b/apps/docs/docs/ref/python/introduction.mdx index cf0a9f0ee4f..cecb5e86669 100644 --- a/apps/docs/docs/ref/python/introduction.mdx +++ b/apps/docs/docs/ref/python/introduction.mdx @@ -19,3 +19,14 @@ hideTitle: true changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
+ +
+

The Python client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.

+ +

+ Huge thanks to official maintainers, [anand2312](https://github.com/anand2312/), [dreinon](https://github.com/dreinon), [J0](https://github.com/j0), and [Leynier](https://github.com/leynier). +

+

+ Shoutout to [timkpaine](https://github.com/timkpaine) for maintaining our Conda libraries as well. +

+
diff --git a/apps/docs/docs/ref/self-hosting-analytics/introduction.mdx b/apps/docs/docs/ref/self-hosting-analytics/introduction.mdx index 5d6efe41eda..ccf1ee1c932 100644 --- a/apps/docs/docs/ref/self-hosting-analytics/introduction.mdx +++ b/apps/docs/docs/ref/self-hosting-analytics/introduction.mdx @@ -26,8 +26,57 @@ However, it's important to note that certain [differences](#differences) may ari ## Getting Started +### Pre-requisites + +Logflare currently requires BigQuery usage. You will need to create a Google Cloud project with billing enabled. + +The requirements are as follows after creating the project: + +- Project ID +- Project number +- A service account key + +#### Setting up BigQuery Service Account + +To ensure that you have sufficient permissions to insert into your Google Cloud BigQuery, ensure that you have created a service account with either: + +- BigQuery Admin role; or +- The following permissions: + - bigquery.datasets.create + - bigquery.datasets.get + - bigquery.datasets.getIamPolicy + - bigquery.datasets.update + - bigquery.jobs.create + - bigquery.routines.create + - bigquery.routines.update + - bigquery.tables.create + - bigquery.tables.delete + - bigquery.tables.get + - bigquery.tables.getData + - bigquery.tables.update + - bigquery.tables.updateData + +You can create the service account via the web console or `gcloud`, as per the [Google Cloud documentation](https://cloud.google.com/iam/docs/keys-create-delete). In the web console, you can create the key by navigating to IAM > Service Accounts > Actions (dropdown) > Manage Keys + +We recommend setting the BigQuery Admin role, as it simplifies permissions setup. + +#### Downloading the Service Account key + +After the service account is created, you will need to create a key for the service account. This key will sign the JWTs for API requests that the Analytics server makes with BigQuery. + +### Docker Compose + Using the example [self-hosting stack based on docker-compose](https://github.com/supabase/supabase/tree/master/docker), you include the logging related services using the following command +You will first need to update the `.env.example` file with the necessary environment variables. + +- `GOOGLE_PROJECT_ID` +- `GOOGLE_PROJECT_NUMBER` + +You will need to place your Service Account key in your present working directory with the filename `gcloud.json`. + +Thereafter, you can run the docker-compose commands and include the logging-specific compose file. + ```bash # assuming you clone the supabase/supabase repo. cd docker @@ -41,6 +90,18 @@ This would include two additional docker services to your compose stack: Logflar server. +#### Vector Usage + +In the Docker Compose example, we utilize vector to coordinate the logging pipeline between the services. However, if you peer into the [vector configuration file](https://github.com/supabase/supabase/blob/master/docker/volumes/logs/vector.yml), you will be able to see that Vector sends logs to the Analytics ingestion endpoint. + +If you need to customize the logging pipeline for your own needs, you must ensure that the payloads matches the expected event schema structure. Without the correct structure, it would cause the Studio Logs UI features to break. + +### Standalone Docker Container + +If desired, you can utilize the standalone docker-container. Please refer to the [docker-compose file](https://github.com/supabase/supabase/tree/master/docker/docker-compose-logging.yml) for required docker configuration. + +Additional supplementary technical documentation on self-hosting and using the `supabase/logflare` image for a full Logflare experience is available at the [official Logflare documentation](https://docs.logflare.app/self-hosting/). + ## Differences API logs rely on Kong instead of the Supabase Cloud API Gateway. Logs from Kong are not enriched with platform-only data. @@ -98,7 +159,6 @@ The self-hosted example is only used as a minimal example on running the entire ### Additional links - [Source code](https://github.com/logflare/logflare) - - [Analytics guides](/docs/guides/analytics) - TODO - [OpenAPI docs](https://logflare.app/api/openapi) - [Supabase Acquires Logflare](https://supabase.com/blog/supabase-acquires-logflare) - [Logflare self-hosting docs](https://docs.logflare.app/self-hosting) diff --git a/apps/docs/generator/helpers.ts b/apps/docs/generator/helpers.ts index 6bb5653e808..e7796c9101f 100644 --- a/apps/docs/generator/helpers.ts +++ b/apps/docs/generator/helpers.ts @@ -2,6 +2,7 @@ import * as _ from 'lodash' import * as fs from 'fs' export const slugify = (text: string) => { + if (!text) return '' return text .toString() .toLowerCase() diff --git a/apps/docs/layouts/SiteLayout.tsx b/apps/docs/layouts/SiteLayout.tsx index 68c0b88d87b..0af5320a944 100644 --- a/apps/docs/layouts/SiteLayout.tsx +++ b/apps/docs/layouts/SiteLayout.tsx @@ -314,20 +314,6 @@ const NavContainer = memo(function NavContainer() { }) const SiteLayout = ({ children }) => { - // const mobileMenuOpen = useMenuMobileOpen() - - useEffect(() => { - const key = localStorage.getItem('supabaseDarkMode') - if (!key) { - // Default to dark mode if no preference config - document.documentElement.className = 'dark' - document.documentElement.style.colorScheme = 'dark' - } else { - document.documentElement.className = key === 'true' ? 'dark' : '' - document.documentElement.style.colorScheme = key === 'true' ? 'dark' : '' - } - }, []) - return ( <> diff --git a/apps/docs/layouts/tutorials/TutorialLayout.tsx b/apps/docs/layouts/tutorials/TutorialLayout.tsx index 2f58a612cca..ba3ffe68f6e 100644 --- a/apps/docs/layouts/tutorials/TutorialLayout.tsx +++ b/apps/docs/layouts/tutorials/TutorialLayout.tsx @@ -14,19 +14,8 @@ interface Props { } const Layout: FC = (props: Props) => { - // const contentString = renderToString(props.children) const [active, setActive] = useState(false) - useEffect(() => { - const key = localStorage.getItem('supabaseDarkMode') - if (!key) { - // Default to dark mode if no preference config - document.documentElement.className = 'dark' - } else { - document.documentElement.className = key === 'true' ? 'dark' : '' - } - }, []) - useEffect(() => { setTimeout(function () { setActive(true) diff --git a/apps/docs/lib/docs.ts b/apps/docs/lib/docs.ts index daa0f83d6bd..2ae4f8dfe80 100644 --- a/apps/docs/lib/docs.ts +++ b/apps/docs/lib/docs.ts @@ -2,7 +2,7 @@ import fs from 'fs' import { join } from 'path' import matter from 'gray-matter' import nonGeneratedReferencePages from 'data/nonGeneratedReferencePages' -import { REFERENCES } from 'components/Navigation/Navigation.constants' +import { REFERENCES } from '~/components/Navigation/NavigationMenu/NavigationMenu.constants' const docsDirectory = process.cwd() diff --git a/apps/docs/lib/refGenerator/helpers.ts b/apps/docs/lib/refGenerator/helpers.ts index 83864fa5540..3325d45786f 100644 --- a/apps/docs/lib/refGenerator/helpers.ts +++ b/apps/docs/lib/refGenerator/helpers.ts @@ -267,6 +267,7 @@ export function gen_v3(spec: OpenAPIV3.Document, dest: string, { apiUrl }: { api } const slugify = (text: string) => { + if (!text) return '' return text .toString() .toLowerCase() diff --git a/apps/docs/pages/_app.tsx b/apps/docs/pages/_app.tsx index 58f7fea9f7b..b8ebabf1003 100644 --- a/apps/docs/pages/_app.tsx +++ b/apps/docs/pages/_app.tsx @@ -24,7 +24,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { IS_PLATFORM || LOCAL_SUPABASE ? createBrowserSupabaseClient() : undefined ) - function telemetry(route: string) { + function handlePageTelemetry(route: string) { return post(`https://api.supabase.io/platform/telemetry/page`, { referrer: document.referrer, title: document.title, @@ -37,7 +37,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { /* * handle telemetry */ - telemetry(url) + handlePageTelemetry(url) /* * handle "scroll to top" behaviour on route change */ @@ -59,6 +59,15 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { } }, [router.events]) + useEffect(() => { + /** + * Send page telemetry on first page load + */ + if (router.isReady) { + handlePageTelemetry(router.route) + } + }, [router.isReady]) + const SITE_TITLE = 'Supabase Documentation' const AuthContainer = (props) => { @@ -78,7 +87,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { ( + MarkdownHandler={(props) => ( )} > diff --git a/apps/docs/pages/_document.tsx b/apps/docs/pages/_document.tsx index ed9a8a70c10..67f89450efd 100644 --- a/apps/docs/pages/_document.tsx +++ b/apps/docs/pages/_document.tsx @@ -2,11 +2,11 @@ import { Html, Head, Main, NextScript } from 'next/document' export default function Document() { return ( - + - +
diff --git a/apps/docs/pages/guides/api.mdx b/apps/docs/pages/guides/api.mdx index 8e45a1243c7..3b518ced47b 100644 --- a/apps/docs/pages/guides/api.mdx +++ b/apps/docs/pages/guides/api.mdx @@ -11,8 +11,8 @@ export const meta = { Supabase auto-generates three types of API directly from your database schema. - REST - connect to your database through a restful interface, directly from the browser. -- Realtime - for listening to database changes. -- GraphQL - a fast and auto-generate GraphQL API. +- GraphQL - manipulate your database using a graph-like query language. +- Realtime - listen to database changes. All the APIs are auto-generated from your database and are designed to get you building as fast as possible, without writing a single line of code. @@ -29,17 +29,40 @@ You can use them directly from the browser (two-tier architecture), or as a comp ## REST API [#rest-api-overview] Supabase provides a RESTful API using [PostgREST](https://postgrest.org/). This is a very thin API layer on top of Postgres. -It provides everything you need from a CRUD API: +It provides everything you need from a CRUD API at the URL `https://.supabase.co/rest/v1/`. -- Basic CRUD operations -- Deeply nested joins, allowing you to fetch data from multiple tables in a single fetch -- Works with Postgres Views +The REST interface is automatically reflected from your database's schema and supports: +- Basic CRUD operations (Create/Read/Update/Delete) +- Arbitrarily deep relationships among tables/views, functions that return table types can also nest related tables/views. +- Works with Postgres Views, Materialized Views and Foreign Tables - Works with Postgres Functions +- User defined computed columns and computed relationships - Works with the Postgres security model - including Row Level Security, Roles, and Grants. +The REST API resolves all requests to a single SQL statement leading to fast response times and high throughput. + +Reference: +- [Docs](https://postgrest.org/) +- [Source Code](https://github.com/PostgREST/postgrest) + ## GraphQL API [#graphql-api-overview] -GraphQL in Supabase works through [pg_graphql](https://supabase.com/blog/pg-graphql), an open source PostgreSQL extension for GraphQL. +Supabase uses [pg_graphql](https://supabase.github.io/pg_graphql/) to expose a GraphQL API endpoint at `https://.supabase.co/graphql/v1/`. +You can introspect and query the GraphQL API of an existing Supabase project within Studio [here](https://app.supabase.com/project/_/api/graphiql), +or navigate there manually at `API Docs > GraphQL > GraphiQL`. + +The GraphQL interface is automatically reflected from your database's schema and supports: +- Basic CRUD operations (Create/Read/Update/Delete) +- Support for Tables, Views, Materialized Views, and Foreign Tables +- Arbitrarily deep relationships among tables/views +- User defined computed fields +- The Postgres security model - including Row Level Security, Roles, and Grants. + +The GraphQL API resolves all requests in a single round-trip leading to fast response times and high throughput. + +Reference: +- [Docs](https://supabase.github.io/pg_graphql/) +- [Source Code](https://github.com/supabase/pg_graphql) ## Realtime API [#realtime-api-overview] diff --git a/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx b/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx index 52f2e5acf68..e52a421e9cb 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/auth-ui.mdx @@ -66,7 +66,7 @@ const App = () => ( ### Social Providers -The Auth component also supports login with [offical social providers](../../auth#providers). +The Auth component also supports login with [official social providers](../../auth#providers). ```js lines=13 title=/src/index.js import { createClient } from '@supabase/supabase-js' @@ -322,6 +322,8 @@ const App = () => ( ) ``` +Currently, translating error messages (e.g. "Invalid credentials") is not supported. Check [related issue.](https://github.com/supabase/auth-ui/issues/86) + export const Page = ({ children }) => export default Page diff --git a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx index 5ffe6f1b488..424ed29ee0a 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx @@ -199,8 +199,8 @@ For [row level security](https://supabase.com/docs/guides/auth/row-level-securit let loadedData = []; async function loadData() { - const { data } = await data.supabase.from('test').select('*').limit(20); - loadedData = data; + const { data: result } = await data.supabase.from('test').select('*').limit(20); + loadedData = result; } $: if (data.session) { @@ -367,7 +367,6 @@ protect multiple routes at once. ```ts // src/hooks.server.ts import type { RequestHandler } from './$types'; -import { getSupabase } from '@supabase/auth-helpers-sveltekit'; import { redirect, error } from '@sveltejs/kit'; export const handle: Handle = async ({ event, resolve }) => { diff --git a/apps/docs/pages/guides/auth/phone-login/twilio.mdx b/apps/docs/pages/guides/auth/phone-login/twilio.mdx index be9da960411..11bca2d9bac 100644 --- a/apps/docs/pages/guides/auth/phone-login/twilio.mdx +++ b/apps/docs/pages/guides/auth/phone-login/twilio.mdx @@ -1,5 +1,4 @@ import Layout from '~/layouts/DefaultGuideLayout' - export const meta = { id: 'auth-twilio', title: 'Phone Auth with Twilio', @@ -295,6 +294,56 @@ and the response should also be the same as above: The user does not have a password therefore will need to sign in via this method each time they want to access your service. +## WhatsApp OTP Logins + +In some cases, you may wish to use WhatsApp as a delivery channel instead. Here are some examples our users have cited: + +- You want higher deliverability +- You wish for a secure channel +- Your users mostly use WhatsApp as a messaging platform + +To make use of WhatsApp OTP, please complete the following steps: + +- Go through the [Twilio self sign up guide for WhatsApp](https://www.twilio.com/docs/whatsapp/self-sign-up) +- Submit a template via the [Twilio Guide For Submitting WhatsApp templates](https://www.twilio.com/docs/whatsapp/tutorial/send-whatsapp-notification-messages-templates#creating-message-templates-and-submitting-them-for-approval) + + +The message template submitted to Twilio must exactly match the SMS Body entered on the Supabase dashboard. + + +The sign in process with WhatsApp is similar to the sign in process for SMS. Do note the additional `whatsapp` parameter added: + +```js +const {data, error } = await supabase.auth.signInWithOtp({ + phone: '+57336567365', + options: { + channel:'whatsapp' + } + }) +``` + +You can also sign up with `whatsapp` as a channel: + +```js +const {data, error }= await supabase.auth.signUp({ + phone: '+57336567365', + password: 'testsupabasenow', + options: { + channel:'whatsapp', + } +}) +``` + +There is no change in the verification process, you should continue to use the `sms` type for verification +```js +// After receiving a WhatsApp OTP +let { data, error } = await supabase.auth.verifyOtp({ + phone: '+57336567365', + token: '123456', + type: 'sms', +}) +``` + ## Resources - [Twilio Signup](https://www.twilio.com/try-twilio) diff --git a/apps/docs/pages/guides/auth/server-side-rendering.mdx b/apps/docs/pages/guides/auth/server-side-rendering.mdx index 87235781711..7b7edb00924 100644 --- a/apps/docs/pages/guides/auth/server-side-rendering.mdx +++ b/apps/docs/pages/guides/auth/server-side-rendering.mdx @@ -1,4 +1,5 @@ import Layout from '~/layouts/DefaultGuideLayout' +import { Accordion } from 'ui' export const meta = { id: 'server-side-rendering', @@ -55,8 +56,25 @@ like `*` and `**` to allow redirects to different forms of URLs. -These redirect URLs have the following structure: +Supabase Auth supports two authentication flows: **Implicit** and **PKCE**. The **PKCE** flow is generally preferred when on the server. +It introduces a few additional steps which guard a against replay and URL capture attacks. Unlike the implicit flow, it also allows users to access the +`access_token` and `refresh_token` on the server. + +
+ Implicit} + id={`ssr-implicit-flow`} + > + +When using the implicit flow, a redirect URL will be returned with the following structure: ``` https://yourapp.com/...#access_token=<...>&refresh_token=<...>&... ``` @@ -77,9 +95,76 @@ your direct control (such as on GitHub Pages or other freemium hosting providers), we want to prevent hosting services from getting access to your user's authorization credentials by default. Even if the server is under your direct control, `GET` requests and their full URLs are often logged. This -approach also avoids leaking credentials in request or access logs. - +approach also avoids leaking credentials in request or access logs. If you wish to obtain the +`access_token` and `refresh_token` on a server, please consider using the PKCE flow. + +
+
+ PKCE} + id={`ssr-pkce-flow`} + > + When using the PKCE flow, a redirect URL will be returned with the following structure: + ``` + https://yourapp.com/...?code=<...> + ``` + The `code` parameter is commonly known as the Auth Code and can be exchanged for an access token by calling `exchangeCodeForSession(code)`. + + For security purposes, the code has a validity of 5 minutes and can only be exchanged for an access token once. You + will need to restart the authentication flow from scratch if you wish to obtain a new access token. + + + As the flow is run server side, `localStorage` may not be available. You may configure the client library to use a custom storage adapter an alternate backing storage such as cookies + by setting the `storage` option to an object with the following methods: + ```js + const customStorageAdapter: SupportedStorage = { + getItem: (key) => { + if (!supportsLocalStorage()) { + // Configure alternate storage + return null + } + return globalThis.localStorage.getItem(key) + }, + setItem: (key, value) => { + if (!supportsLocalStorage()) { + // Configure alternate storage here + return + } + globalThis.localStorage.setItem(key, value) + }, + removeItem: (key) => { + if (!supportsLocalStorage()) { + // Configure alternate storage here + return + } + globalThis.localStorage.removeItem(key) + }, + } + ``` + You may also configure the client library to automatically exchange it for a session after a successful redirect. This can be done by setting the `detectSessionInUrl` option to `true`. + + Putting it all together, your client library initialization may look like this: + ```js + const supabase = createClient( + 'https://xyzcompany.supabase.co', + 'public-anon-key', + options: { + ... + auth: { + ... + detectSessionInUrl: true, + flowType: 'pkce', + storage: customStorageAdapter, + } + ... + } + ) + ``` + You can read more about the PKCE flow [here](https://oauth.net/2/pkce/) + +
+
## Bringing it together @@ -220,3 +305,4 @@ cache keys every hour or less. export const Page = ({ children }) => export default Page + diff --git a/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx b/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx index d5598cc9c7a..1923b2ed243 100644 --- a/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx +++ b/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx @@ -21,6 +21,8 @@ If you're having issues with identity provider software not on this list, please This guide requires the use of the [Supabase CLI](/docs/guides/cli). Please make sure you're using version v1.46.4 or higher. You can use `supabase -v` to see the currently installed version. +You can use the `supabase sso` [subcommands](/docs/reference/cli/supabase-sso) to manage your project's configuration. + SAML 2.0 support is disabled by default on Supabase projects. You can configure this on the [Auth Providers](https://app.supabase.com/project/_/auth/providers) page on your project. Please note that SAML 2.0 support is offered on tiers Pro and above. Check the [Pricing](https://supabase.com/pricing) page for more information. @@ -67,7 +69,7 @@ Note that SLO (Single Logout) is not supported at this time with Supabase Auth a Append `?download=true` to the Metadata URL to download the Metadata XML file. This is useful in cases where the identity provider requires a file. -Alternatively, you can use the `supabase sso info --project-ref ` command to get setup information for your project. +Alternatively, you can use the `supabase sso info --project-ref ` [command](/docs/reference/cli/supabase-sso-info) to get setup information for your project. ### User accounts and identities @@ -129,7 +131,7 @@ CREATE POLICY "View organization settings." ## Managing SAML 2.0 connections -Once you've enabled SAML 2.0 support on your project via the [Auth Providers](https://app.supabase.com/project/_/auth/providers) page in the dashboard, you can use the [Supabase CLI](/docs/guides/cli) to add, update, remove and view information about identity providers. +Once you've enabled SAML 2.0 support on your project via the [Auth Providers](https://app.supabase.com/project/_/auth/providers) page in the dashboard, you can use the [Supabase CLI](/docs/reference/cli/supabase-sso) to add, update, remove and view information about identity providers. ### Add a connection @@ -152,7 +154,7 @@ Commonly used SAML 2.0 Identity Providers that only support Metadata XML files: - Google Workspaces (GSuite) - Any self-hosted or on-prem identity provider behind a VPN -Once you've obtained the SAML 2.0 Metadata XML file or URL you can establish a connection with your project's Supabase Auth server by using the [Supabase CLI](/docs/guides/cli): +Once you've obtained the SAML 2.0 Metadata XML file or URL you can [establish a connection](/docs/reference/cli/supabase-sso-add) with your project's Supabase Auth server by running: ```bash supabase sso add --type saml --project-ref \ @@ -185,7 +187,7 @@ supabase.auth.signInWithSSO({ }) ``` -Calling this method starts the sign-in process using the identity provider registered for the `company.com` domain name. It is not required that identity providers be assigned one or multiple domain names, in which case you can use the provider's unique ID instead. +Calling [`signInWithSSO`](/docs/reference/javascript/auth-signinwithsso) starts the sign-in process using the identity provider registered for the `company.com` domain name. It is not required that identity providers be assigned one or multiple domain names, in which case you can use the provider's unique ID instead. ### Understanding attribute mappings @@ -272,7 +274,7 @@ Most SAML 2.0 identity providers use Lightweight Directory Access Protocol (LDAP ### Remove a connection -Once a connection to an identity provider is established, you can remove it by running: +Once a connection to an identity provider is established, you can [remove it](/docs/reference/cli/supabase-sso-remove) by running: ```bash supabase sso remove --project-ref @@ -282,7 +284,7 @@ If successful, the details of the removed identity provider will be shown. All u If you need to reassign those user accounts to another identity provider, please [open a support ticket](https://app.supabase.com/support/new). -A list of all registered identity providers can be displayed by running: +A [list of all](/docs/reference/cli/supabase-sso-list) registered identity providers can be displayed by running: ```bash supabase sso list --project-ref @@ -299,7 +301,7 @@ Commonly this is necessary when: - Other SAML 2.0 Metadata attributes have changed, but it is still the same identity provider - You are updating the domains or attribute mapping -You can use the following command to update the configuration of an identity provider: +You can use this command to [update](/docs/reference/cli/supabase-sso-update) the configuration of an identity provider: ```bash supabase sso update --project-ref @@ -311,13 +313,13 @@ It is not possible to change the unique SAML identifier of the identity provider ### Retrieving information about a connection -You can always obtain a list of all registered providers using: +You can always obtain a [list](/docs/reference/cli/supabase-sso-list) of all registered providers using: ```bash supabase sso list --project-ref ``` -This list will only include basic information about each provider. To see all of the information about a provider you can use: +This list will only include basic information about each provider. To see [all of the information](/docs/reference/cli/supabase-sso-show) about a provider you can use: ```bash supabase sso show --project-ref diff --git a/apps/docs/pages/guides/cli/local-development.mdx b/apps/docs/pages/guides/cli/local-development.mdx index f6fe7c6c637..ca38587e786 100644 --- a/apps/docs/pages/guides/cli/local-development.mdx +++ b/apps/docs/pages/guides/cli/local-development.mdx @@ -412,14 +412,15 @@ supabase functions deploy Local logs rely on the Supabase Analytics Server. This can be enabled via the CLI configuration, and requires a Google Cloud project and BigQuery access. -The Google Cloud project must have billing enabled. + The Google Cloud project must have billing enabled. Read more about this requirement + [here](https://supabase.com/docs/reference/self-hosting-analytics/introduction#bigquery). Requirements: -1. Google Cloud project number (must be paid) +1. Google Cloud project number 2. Google Cloud project ID -3. Google Cloud JWT, obtained through [Google Cloud IAM dashboard](https://console.cloud.google.com/iam-admin/iam) +3. Google Cloud Service Account Key, obtained through [Google Cloud IAM dashboard](https://console.cloud.google.com/iam-admin/iam), with BigQuery Admin role assigned to the service account. Once you have these 3 items, follow these steps: @@ -433,7 +434,7 @@ gcp_project_id = "my-project-id" gcp_jwt_path = "supabase/gcloud.json" ``` -2. Place your JWT file at the corresponding path and ensure that it is correctly named. +2. Place your service account key (a JSON file) at the corresponding path and ensure that it is correctly named. 3. Start your local stack using `supabase start` diff --git a/apps/docs/pages/guides/database/arrays.mdx b/apps/docs/pages/guides/database/arrays.mdx index e59be4a6d4b..45f258195e4 100644 --- a/apps/docs/pages/guides/database/arrays.mdx +++ b/apps/docs/pages/guides/database/arrays.mdx @@ -140,7 +140,7 @@ returns: This returns the entire array field: ```js -const { data, error } = await supabase.from('arraytest').select('textarray[]') +const { data, error } = await supabase.from('arraytest').select('textarray') console.log(JSON.stringify(data, null, 2)) ``` @@ -161,7 +161,7 @@ returns: - [Supabase JS Client](https://github.com/supabase/supabase-js) - [Supabase Account - Free Tier OK](https://supabase.com) -- [PostgreSQL Arrays](https://www.postgresql.org/docs/12/arrays.html) +- [PostgreSQL Arrays](https://www.postgresql.org/docs/15/arrays.html) export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/functions/cicd-workflow.mdx b/apps/docs/pages/guides/functions/cicd-workflow.mdx index 01c30d69182..a1836c20b53 100644 --- a/apps/docs/pages/guides/functions/cicd-workflow.mdx +++ b/apps/docs/pages/guides/functions/cicd-workflow.mdx @@ -2,7 +2,7 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'cicd-workflow', - title: 'Deploying with Git', + title: 'Deploying with GitHub Actions', description: 'How to deploy Supabase Edge Functions with a CI / CD pipeline.', video: 'https://www.youtube.com/v/6OMVWiiycLs', } diff --git a/apps/docs/pages/guides/getting-started.mdx b/apps/docs/pages/guides/getting-started.mdx index bb335915b34..8032bc3a0b8 100644 --- a/apps/docs/pages/guides/getting-started.mdx +++ b/apps/docs/pages/guides/getting-started.mdx @@ -36,6 +36,22 @@ export const meta = {
+### Use Cases + +
+ {useCases.map((item) => { + return ( + + + + {item.description} + + + + ) + })} +
+ ### Framework Quickstarts
@@ -84,6 +100,27 @@ export const meta = { })}
+export const useCases = [ + { + title: 'OpenAI Vector Search', + href: '/guides/getting-started/openai/vector-search', + description: `Build your own custom ChatGPT with Next.js, OpenAI and pg_vector.`, + icon: '/docs/img/icons/openai_logo', + }, + { + title: 'Subscription Payments (SaaS)', + href: 'https://github.com/vercel/nextjs-subscription-payments#nextjs-subscription-payments-starter', + description: `Clone, deploy, and fully customize a SaaS subscription application with Next.js.`, + icon: '/docs/img/icons/nextjs-icon', + }, + { + title: 'Partner Gallery', + href: 'https://github.com/supabase-community/partner-gallery-example#supabase-partner-gallery-example', + description: `Postgres full-text search, image storage, and more.`, + icon: '/docs/img/icons/nextjs-icon', + }, +] + export const quickstarts = [ { title: 'React', @@ -99,6 +136,13 @@ export const quickstarts = [ 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a NextJS app.', icon: '/docs/img/icons/nextjs-icon', }, + { + title: 'RedwoodJS', + href: '/guides/getting-started/quickstarts/redwoodjs', + description: + 'Learn how to create a Supabase project, add some sample data to your database using Prisma migration and seeds, and query the data from a RedwoodJS app.', + icon: '/docs/img/icons/redwood-icon', + }, { title: 'Flutter', href: '/guides/getting-started/quickstarts/flutter', @@ -106,6 +150,27 @@ export const quickstarts = [ 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Flutter app.', icon: '/docs/img/icons/flutter-icon', }, + { + title: 'SvelteKit', + href: '/guides/getting-started/quickstarts/sveltekit', + description: + 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a SvelteKit app.', + icon: '/docs/img/icons/svelte-icon', + }, + { + title: 'SolidJS', + href: '/guides/getting-started/quickstarts/solidjs', + description: + 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a SolidJS app.', + icon: '/docs/img/icons/solidjs-icon', + }, + { + title: 'Vue', + href: '/guides/getting-started/quickstarts/solidjs', + description: + 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Vue app.', + icon: '/docs/img/icons/vuejs-icon', + }, ] export const webapps = [ diff --git a/apps/docs/pages/guides/getting-started/openai/vector-search.mdx b/apps/docs/pages/guides/getting-started/openai/vector-search.mdx new file mode 100644 index 00000000000..580d52262e0 --- /dev/null +++ b/apps/docs/pages/guides/getting-started/openai/vector-search.mdx @@ -0,0 +1,544 @@ +import Layout from '~/layouts/DefaultGuideLayout' +import StepHikeCompact from '~/components/StepHikeCompact' + +export const meta = { + title: 'OpenAI Embeddings & Vector Search', + subtitle: + 'Learn how to build a ChatGPT-style doc search powered by Next.js, OpenAI, and Supabase.', + breadcrumb: 'OpenAI', +} + +In this tutorial we'll look at how you can build a custom ChatGPT-like search experience for your own knowledge base. See our [Supabase Clippy](https://supabase.com/blog/chatgpt-supabase-docs) blog post for an example of how this will look. + +We assume that you have a Next.js project with a collection of `.mdx` files nested inside your `pages` directory. We will start developing locally with the Supabase CLI and then push our local database changes to our hosted Supabase project. + + + You can find the [full example on + GitHub](https://github.com/supabase-community/nextjs-openai-doc-search). + + +## Create a project + +1. [Create a new project](https://app.supabase.com) in the Supabase Dashboard. +1. Enter your project details. +1. Wait for the new database to launch. + +## Prepare the database + +Let's prepare the database schema. We can use the "OpenAI Vector Search" quickstart in the [SQL Editor](https://app.supabase.com/project/_/sql), or you can copy/paste the SQL below and run it yourself. + + + + +1. Go to the [SQL Editor](https://app.supabase.com/project/_/sql) page in the Dashboard. +2. Click **OpenAI Vector Search**. +3. Click **Run**. + + + + + + + + + + + Make sure you have the latest version of the [Supabase CLI installed](). + + Initialize Supabase in the root directory of your app. + + + + + + ```bash + supabase init + ``` + + + + + + + + + To make changes to our local database, we need to create a new migration. This will create a new `.sql` file in our `supabase/migrations` folder, where we can write SQL that will be applied to our local database when starting Supabase locally. + + + + + + ```bash + supabase migration new init + ``` + + + + + + + + + Copy the following SQL line into the newly created migration file to enable the pgvector extension. + + + + + + ```sql + -- Enable pgvector extension + create extension if not exists vector with schema public; + ``` + + + + + + + + + Copy these SQL queries to your migration file. It will create two tables in our database schema. + + + + + + ```sql + -- Stores the checksum of our pages. + -- This ensures that we only regenerate embeddings + -- when the page content has changed. + create table "public"."nods_page" ( + id bigserial primary key, + parent_page_id bigint references public.nods_page, + path text not null unique, + checksum text, + meta jsonb, + type text, + source text + ); + alter table "public"."nods_page" + enable row level security; + + -- Stores the actual embeddings with some metadata + create table "public"."nods_page_section" ( + id bigserial primary key, + page_id bigint not null references public.nods_page on delete cascade, + content text, + token_count int, + embedding vector(1536), + slug text, + heading text + ); + alter table "public"."nods_page_section" + enable row level security; + ``` + + + + + + + + + Anytime the user sends a query, we want to find the content that's relevant to their questions. We can do this using pgvector's similarity search. + + These are quite complex SQL operations, so let's wrap them in database functions that we can call from our frontend using [RPC](https://supabase.com/docs/reference/javascript/rpc). + + + + + + ```sql + -- Create embedding similarity search functions + create or replace function match_page_sections( + embedding vector(1536), + match_threshold float, + match_count int, + min_content_length int + ) + returns table ( + id bigint, + page_id bigint, + slug text, + heading text, + content text, + similarity float + ) + language plpgsql + as $$ + #variable_conflict use_variable + begin + return query + select + nods_page_section.id, + nods_page_section.page_id, + nods_page_section.slug, + nods_page_section.heading, + nods_page_section.content, + (nods_page_section.embedding <#> embedding) * -1 as similarity + from nods_page_section + + -- We only care about sections that have a useful amount of content + where length(nods_page_section.content) >= min_content_length + + -- The dot product is negative because of a Postgres limitation, so we negate it + and (nods_page_section.embedding <#> embedding) * -1 > match_threshold + + -- OpenAI embeddings are normalized to length 1, so + -- cosine similarity and dot product will produce the same results. + -- Using dot product which can be computed slightly faster. + -- + -- For the different syntaxes, see https://github.com/pgvector/pgvector + order by nods_page_section.embedding <#> embedding + + limit match_count; + end; + $$; + ``` + + + + + + + + + Start Supabase locally. At this point all files in `supabase/migrations` will be applied to your database and you're ready to go. + + + + + + ```bash + supabase start + ``` + + + + + + + + + Once ready, you can link your local project to your cloud hosted Supabase project and push the local changes to your hosted instance. + + + + + + ```bash + supabase link --project-ref=your-project-ref + + supabase db push + ``` + + + + + + + + + + +## Pre-process the knowledge base at build time + +With our database set up, we need to process and store all `.mdx` files in the `pages` directory. You can find the full script [here](https://github.com/supabase-community/nextjs-openai-doc-search/blob/main/lib/generate-embeddings.ts), or follow the steps below: + + + + + + + Create a new file `lib/generate-embeddings.ts` and copy the code over from [GitHub](https://github.com/supabase-community/nextjs-openai-doc-search/blob/main/lib/generate-embeddings.ts). + + + + + + ```bash + curl \ + https://raw.githubusercontent.com/supabase-community/nextjs-openai-doc-search/main/lib/generate-embeddings.ts \ + -o "lib/generate-embeddings.ts" + ``` + + + + + + + + + We need some environment variables to run the script. Add them to your `.env` file and make sure your `.env` file is not committed to source control! + You can get your local Supabase credentials by running `supabase status`. + + + + + + ```txt + NEXT_PUBLIC_SUPABASE_URL= + NEXT_PUBLIC_SUPABASE_ANON_KEY= + SUPABASE_SERVICE_ROLE_KEY= + + # Get your key at https://platform.openai.com/account/api-keys + OPENAI_KEY= + ``` + + + + + + + + + Include the script in your `package.json` script commands to enable Vercel to automaticall run it at build time. + + + + + + ```json + "scripts": { + "dev": "next dev", + "build": "pnpm run embeddings && next build", + "start": "next start", + "embeddings": "tsx lib/generate-embeddings.ts" + }, + ``` + + + + + + + +## Create Text Completion with OpenAI API + +Anytime a user asks a question, we need to create an embedding for their question, perform a similarity search, and then send a text completion request to the OpenAI API with the query and then context content merged together into a prompt. + +All of this is glued together in a [Vercel Edge Function](https://vercel.com/docs/concepts/functions/edge-functions), the code for which can be found on [GitHub](https://github.com/supabase-community/nextjs-openai-doc-search/blob/main/pages/api/vector-search.ts). + + + + + + + In order to perform similarity search we need to turn the question into an embedding. + + + + + + ```ts + const embeddingResponse = await fetch('https://api.openai.com/v1/embeddings', { + method: 'POST', + headers: { + Authorization: `Bearer ${openAiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + model: 'text-embedding-ada-002', + input: sanitizedQuery.replaceAll('\n', ' '), + }), + }) + + if (embeddingResponse.status !== 200) { + throw new ApplicationError('Failed to create embedding for question', embeddingResponse) + } + + const { + data: [{ embedding }], + } = await embeddingResponse.json() + ``` + + + + + + + + + Using the `embeddingResponse` we can now perform similarity search by performing an remote procedure call (RPC) to the database function we created earlier. + + + + + + ```ts + const { error: matchError, data: pageSections } = await supabaseClient.rpc( + 'match_page_sections', + { + embedding, + match_threshold: 0.78, + match_count: 10, + min_content_length: 50, + } + ) + ``` + + + + + + + + + With the relevant content for the user's question identified, we can now build the prompt and make a text completion request via the OpenAI API. + + If successful, the OpenAI API will respond with a `text/event-stream` response that we can simply forward to the client where we'll process the event stream to smoothly print the answer to the user. + + + + + + ```ts + const prompt = codeBlock` + ${oneLine` + You are a very enthusiastic Supabase representative who loves + to help people! Given the following sections from the Supabase + documentation, answer the question using only that information, + outputted in markdown format. If you are unsure and the answer + is not explicitly written in the documentation, say + "Sorry, I don't know how to help with that." + `} + + Context sections: + ${contextText} + + Question: """ + ${sanitizedQuery} + """ + + Answer as markdown (including related code snippets if available): + ` + + const completionOptions: CreateCompletionRequest = { + model: 'text-davinci-003', + prompt, + max_tokens: 512, + temperature: 0, + stream: true, + } + + const response = await fetch('https://api.openai.com/v1/completions', { + method: 'POST', + headers: { + Authorization: `Bearer ${openAiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(completionOptions), + }) + + if (!response.ok) { + const error = await response.json() + throw new ApplicationError('Failed to generate completion', error) + } + + // Proxy the streamed SSE response from OpenAI + return new Response(response.body, { + headers: { + 'Content-Type': 'text/event-stream', + }, + }) + ``` + + + + + + + +## Display the answer on the frontend + +In a last step, we need to process the event stream from the OpenAI API and print the answer to the user. The full code for this can be found on [GitHub](https://github.com/supabase-community/nextjs-openai-doc-search/blob/main/components/SearchDialog.tsx). + +```tsx +const handleConfirm = React.useCallback( + async (query: string) => { + setAnswer(undefined) + setQuestion(query) + setSearch('') + dispatchPromptData({ index: promptIndex, answer: undefined, query }) + setHasError(false) + setIsLoading(true) + + const eventSource = new SSE(`api/vector-search`, { + headers: { + apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? '', + Authorization: `Bearer ${process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY}`, + 'Content-Type': 'application/json', + }, + payload: JSON.stringify({ query }), + }) + + function handleError(err: T) { + setIsLoading(false) + setHasError(true) + console.error(err) + } + + eventSource.addEventListener('error', handleError) + eventSource.addEventListener('message', (e: any) => { + try { + setIsLoading(false) + + if (e.data === '[DONE]') { + setPromptIndex((x) => { + return x + 1 + }) + return + } + + const completionResponse: CreateCompletionResponse = JSON.parse(e.data) + const text = completionResponse.choices[0].text + + setAnswer((answer) => { + const currentAnswer = answer ?? '' + + dispatchPromptData({ + index: promptIndex, + answer: currentAnswer + text, + }) + + return (answer ?? '') + text + }) + } catch (err) { + handleError(err) + } + }) + + eventSource.stream() + + eventSourceRef.current = eventSource + + setIsLoading(true) + }, + [promptIndex, promptData] +) +``` + +## Learn More + +Want to learn more about the awesome tech that is powering this? + +- Read about how we built [ChatGPT for the Supabase Docs](https://supabase.com/blog/chatgpt-supabase-docs). +- Read the pgvector Docs for [Embeddings and vector similarity](https://supabase.com/docs/guides/database/extensions/pgvector) +- Watch Greg's video for a full breakdown: + +
+ +
+ +export const Page = ({ children }) => +export default Page diff --git a/apps/docs/pages/guides/getting-started/quickstarts/redwoodjs.mdx b/apps/docs/pages/guides/getting-started/quickstarts/redwoodjs.mdx new file mode 100644 index 00000000000..ac716078403 --- /dev/null +++ b/apps/docs/pages/guides/getting-started/quickstarts/redwoodjs.mdx @@ -0,0 +1,242 @@ +import Layout from '~/layouts/DefaultGuideLayout' +import StepHikeCompact from '~/components/StepHikeCompact' + +export const meta = { + title: 'Use Supabase with RedwoodJS', + subtitle: + 'Learn how to create a Supabase project, add some sample data to your database using Prisma migration and seeds, and query the data from a RedwoodJS app.', + breadcrumb: 'Framework Quickstarts', +} + + + + + + [Create a new project](https://app.supabase.com) in the Supabase Dashboard. + + + Be sure to make note of the Database Password you used as you will need this later to connect to your database. + + + + + ![New Project](/docs/img/redwoodjs-qs-new-project.png) + + + + + + + After your project is ready, gather the following information about your [database connections](https://app.supabase.com/project/_/settings/database): + + * Connection String (port 5432) + * Connection Pooling / Connection String (port 6543) + + You will need these to setup environment variables in Step 5. + + + You can copy and paste these connection strings from the Supabase Dashboard when needed in later steps. + + + + + ![New Project](/docs/img/redwoodjs-qs-connection-strings-pool.png) + + + + + + + Create a RedwoodJS app with TypeScript. + + + The [`yarn` package manager](https://yarnpkg.com) is required to create a RedwoodJS app. You will use it to run RedwoodJS commands later. + + While TypeScript is recommended, If you want a JavaScript app, omit the `--ts` flag. + + + + + ```bash Terminal + yarn create redwood-app my-app --ts + ``` + + + + + + + You'll develop your app, manage database migrations, and run your app in VS Code. + + + + ```bash Terminal + cd my-app + code . + ``` + + + + + + + In your `.env` file, add the following environment variables for your database connection: + + * The `DIRECT_URL` should use the `Connection String` from your Supabase project. Hint: the port is `5432`. + + * The `DATABASE_URL` should use the `Connection Pooling / Connection String` from your Supabase project. Hint: the port is `6432`. + + + Be sure to append `?pgbouncer=true` to the end of the `DATABASE_URL` connection string. + Also, replace `[YOUR-PASSWORD]` and `[YOUR-PROJECT-REF]` with the password you used when creating your Supabase project and the project reference from the URL in your browser. + + + + + ```bash terminal + # .env + # PostgreSQL connection string used for migrations + DIRECT_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres" + # PostgreSQL connection string with pgBouncer config — used by Prisma Client + DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:6543/postgres?pgbouncer=true" + + ``` + + + + + + + By default, RedwoodJS ships with a SQLite database, but we want to use PostgreSQL. + + Update your Prisma schema file `api/db/schema.prisma` to use your Supabase PostgreSQL database connection environment variables you setup in Step 5. + + + + ```file="api/prisma/schema.prisma" title="api/prisma/schema.prisma" + // api/db/schema.prisma + + datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + directUrl = env("DIRECT_URL") + } + ``` + + + + + + + Create the Country model in `api/db/schema.prisma` and then run `yarn rw prisma migrate dev` from your terminal to apply the migration. + + + + ```js + // api/db/schema.prisma + + model Country { + id Int @id @default(autoincrement()) + name String @unique + } + ``` + + + + + + + Let's seed the database with a few countries. + + Update the file `scripts/seeds.ts` to contain the following code: + + + + + ```ts + // scripts/seed.ts + + import type { Prisma } from '@prisma/client' + import { db } from 'api/src/lib/db' + + export default async () => { + try { + const data: Prisma.CountryCreateArgs['data'][] = [ + { name: 'United States' }, + { name: 'Canada' }, + { name: 'Mexico' }, + ] + + console.log('Seeding countries ...') + + const countries = await db.country.createMany({ data }) + + console.log('Done.', countries) + } catch (error) { + console.error(error) + } + } + ``` + + + + + + + Run the seed database command to populate the `Country` table with the countries you just created. + + + The reset database command `yarn rw prisma db reset` will recreate the tables and will also run the seed script. + + + + + ```bash Terminal + yarn rw prisma db seed + ``` + + + + + + + Now, we'll use RedwoodJS generators to scaffold a CRUD UI for the `Country` model. + + + + ```bash Terminal + yarn rw g scaffold country + ``` + + + + + + + Start the app via `yarn rw dev`. A browser will open to the RedwoodJS Splash page. + + + + ![RedwoodJS Splash Page](/docs/img/redwoodjs-qs-splash.png) + + + + + + + + Click on `/countries` to visit http://localhost:8910/countries where should see the list of countries. + + You may now edit, delete, and add new countries using the scaffolded UI. + + + + ![RedwoodJS Splash Page](/docs/img/redwoodjs-qs-countries-ui.png) + + + + + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/platform/logs.mdx b/apps/docs/pages/guides/platform/logs.mdx index 897f22cebda..f1609a68482 100644 --- a/apps/docs/pages/guides/platform/logs.mdx +++ b/apps/docs/pages/guides/platform/logs.mdx @@ -4,23 +4,16 @@ import { logConstants } from 'shared-data' export const meta = { id: 'logs', title: 'Logging', - description: 'Getting started with Supabase Platform Log Browser', + description: 'Getting started with Supabase Log Browser', } The Supabase Platform includes a Logs Explorer that allows log tracing and debugging. Log retention is based on your [project's pricing plan](https://supabase.com/pricing). - - -These features are not currently available for self-hosting and local development.
-This is on the roadmap and you can follow the progress in the [Logflare repository](https://github.com/Logflare/logflare). - -
- ## Product Logs Supabase provides a logging interface specific to each product. You can use simple regular expressions for keywords and patterns to search log event messages. You can also export and download the log events matching your query as a spreadsheet. -{/* */} +{/* */} diff --git a/apps/docs/pages/guides/resources.mdx b/apps/docs/pages/guides/resources.mdx index c2f2e4aa2a3..5bda171db2f 100644 --- a/apps/docs/pages/guides/resources.mdx +++ b/apps/docs/pages/guides/resources.mdx @@ -141,6 +141,12 @@ export const migrationGuides = [ href: '/guides/resources/migrating-to-supabase/render', description: 'Migrate your Render Postgres database to Supabase.', }, + { + title: 'Amazon RDS', + icon: '/docs/img/icons/aws-rds-icon', + href: '/guides/resources/migrating-to-supabase/amazon-rds', + description: 'Migrate your Amazon RDS database to Supabase.', + }, ] export const postgres = [ diff --git a/apps/docs/pages/guides/resources/migrating-to-supabase/amazon-rds.mdx b/apps/docs/pages/guides/resources/migrating-to-supabase/amazon-rds.mdx new file mode 100644 index 00000000000..6eacca05a42 --- /dev/null +++ b/apps/docs/pages/guides/resources/migrating-to-supabase/amazon-rds.mdx @@ -0,0 +1,95 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + title: 'Migrate from Amazon RDS to Supabase', + description: 'Migrate your Amazon RDS MySQL or MS SQL database to Supabase Postgres database.', +} + +This guide aims to exhibit the process of transferring your Amazon RDS database from any of these engines Postgres, MySQL or MS SQL to Supabase's Postgres database. Although Amazon RDS is a favored managed database service provided by AWS, it may not suffice for all use cases. Supabase, on the other hand, provides an excellent free and open source option that encompasses all the necessary backend features to develop a product: a Postgres database, authentication, instant APIs, edge functions, real-time subscriptions, and storage. + +Supabase's core is Postgres, enabling the use of row-level security and providing access to over 40 Postgres extensions. By migrating from Amazon RDS to Supabase, you can leverage Postgres to its fullest potential and acquire all the features you need to complete your project. + +## Retrieve your Amazon RDS database credentials [#retrieve-rds-credentials] + +1. Log in to your [Amazon RDS account](https://aws.amazon.com/rds/). +1. Select the region where your RDS database is located. +1. Navigate to the **Databases** tab. +1. Select the database that you want to migrate. +1. In the **Connectivity & Security** tab, note down the Endpoint and the port number. +1. In the **Configuration** tab, note down the Database name and the Username. +1. If you do not have the password, create a new one and note it down. + +![Copying RDS credentials from AWS Management Console](/docs/img/guides/resources/migrating-to-supabase/amazon-rds/amazon-rds_credentials.png) + +## Retrieve your Supabase Host [#retrieve-supabase-host] + +1. If you're new to Supabase, [create a project](https://app.supabase.com). +1. Go to the [Database settings](https://app.supabase.com/project/_/settings/database) for your project in the Supabase Dashboard. +1. Under **Connection Info**, note your Host (`$SUPABASE_HOST`). +1. Save your password or [reset it](https://app.supabase.com/project/_/settings/database) if you forgot it. + +![Finding Supabase host address](/docs/img/guides/resources/migrating-to-supabase/amazon-rds/supabase_dashboard.png) + +## Migrate the database + +The fastest way to migrate your database is with the Supabase migration tool on + [Google Colab](https://colab.research.google.com/github/mansueli/Supa-Migrate/blob/main/Amazon_RDS_to_Supabase.ipynb). + +Alternatively, you can use [pgloader](https://github.com/dimitri/pgloader), a flexible and powerful data migration tool that supports a wide range of source database engines, including MySQL and MS SQL, and migrates the data to a Postgres database. For databases using the Postgres engine, we recommend using the pg_dump and psql command line tools, which are included in a full PostgreSQL installation. + + + + +1. Select the Dabase Engine from the Source database in the dropdown +1. Set the environment variables (`HOST`, `USER`, `SOURCE_DB`,`PASSWORD`, `SUPABASE_URL`, and `SUPABASE_PASSWORD`) in the Colab notebook. +2. Run the first two steps in [the notebook](https://colab.research.google.com/github/mansueli/Supa-Migrate/blob/main/Amazon_RDS_to_Supabase.ipynb) in order. The first sets engine and installs the necessary files. +3. Run the third step to start the migration. This will take a few minutes. + + + + + Install pgloader. + Create a configuration file (e.g., config.load). + +```sql +LOAD DATABASE + FROM mysql://USER:PASSWORD@HOST/SOURCE_DB + INTO postgres://postgres:password@db.xxxx.supabase.co:6543/postgres +ALTER SCHEMA 'public' OWNER TO 'postgres'; +set wal_buffers = '64MB', max_wal_senders = 0, statement_timeout = 0, work_mem to '2GB'; +```` + +##Run the migration with pgloader + +```bash +pgloader config.load +``` + + + + Install pgloader. + Create a configuration file (e.g., config.load). + +```sql +LOAD DATABASE + FROM mssql://USER:PASSWORD@HOST/SOURCE_DB + INTO postgres://postgres:password@db.xxxx.supabase.co:6543/postgres +ALTER SCHEMA 'public' OWNER TO 'postgres'; +set wal_buffers = '64MB', max_wal_senders = 0, statement_timeout = 0, work_mem to '2GB'; +```` + +##Run the migration with pgloader + +```bash +pgloader config.load +``` + + + +export const Page = ({ children }) => +export default Page diff --git a/apps/docs/pages/index.mdx b/apps/docs/pages/index.mdx index f1a68b30401..cb079575e9b 100644 --- a/apps/docs/pages/index.mdx +++ b/apps/docs/pages/index.mdx @@ -72,6 +72,11 @@ export const meta = { + + + + + diff --git a/apps/docs/public/favicon/android-chrome-192x192.png b/apps/docs/public/favicon/android-chrome-192x192.png index f45e47c8a9c..f0a2817a744 100644 Binary files a/apps/docs/public/favicon/android-chrome-192x192.png and b/apps/docs/public/favicon/android-chrome-192x192.png differ diff --git a/apps/docs/public/favicon/android-chrome-512x512.png b/apps/docs/public/favicon/android-chrome-512x512.png index 3a0ba2b52aa..1c658733e6e 100644 Binary files a/apps/docs/public/favicon/android-chrome-512x512.png and b/apps/docs/public/favicon/android-chrome-512x512.png differ diff --git a/apps/docs/public/favicon/apple-touch-icon-114x114.png b/apps/docs/public/favicon/apple-touch-icon-114x114.png new file mode 100644 index 00000000000..d2ee4b9e139 Binary files /dev/null and b/apps/docs/public/favicon/apple-touch-icon-114x114.png differ diff --git a/apps/docs/public/favicon/apple-touch-icon-120x120.png b/apps/docs/public/favicon/apple-touch-icon-120x120.png new file mode 100644 index 00000000000..5bd0e4963a1 Binary files /dev/null and b/apps/docs/public/favicon/apple-touch-icon-120x120.png differ diff --git a/apps/docs/public/favicon/apple-touch-icon-144x144.png b/apps/docs/public/favicon/apple-touch-icon-144x144.png new file mode 100644 index 00000000000..c73891c985d Binary files /dev/null and b/apps/docs/public/favicon/apple-touch-icon-144x144.png differ diff --git a/apps/docs/public/favicon/apple-touch-icon-152x152.png b/apps/docs/public/favicon/apple-touch-icon-152x152.png new file mode 100644 index 00000000000..65f90a02357 Binary files /dev/null and b/apps/docs/public/favicon/apple-touch-icon-152x152.png differ diff --git a/apps/docs/public/favicon/apple-touch-icon-57x57.png b/apps/docs/public/favicon/apple-touch-icon-57x57.png new file mode 100644 index 00000000000..bfa1628afb5 Binary files /dev/null and b/apps/docs/public/favicon/apple-touch-icon-57x57.png differ diff --git a/apps/docs/public/favicon/apple-touch-icon-60x60.png b/apps/docs/public/favicon/apple-touch-icon-60x60.png new file mode 100644 index 00000000000..15c5dca8005 Binary files /dev/null and b/apps/docs/public/favicon/apple-touch-icon-60x60.png differ diff --git a/apps/docs/public/favicon/apple-touch-icon-72x72.png b/apps/docs/public/favicon/apple-touch-icon-72x72.png new file mode 100644 index 00000000000..fb0c4fb3663 Binary files /dev/null and b/apps/docs/public/favicon/apple-touch-icon-72x72.png differ diff --git a/apps/docs/public/favicon/apple-touch-icon-76x76.png b/apps/docs/public/favicon/apple-touch-icon-76x76.png new file mode 100644 index 00000000000..7d950f0862e Binary files /dev/null and b/apps/docs/public/favicon/apple-touch-icon-76x76.png differ diff --git a/apps/docs/public/favicon/apple-touch-icon.png b/apps/docs/public/favicon/apple-touch-icon.png deleted file mode 100644 index 330e352fa5f..00000000000 Binary files a/apps/docs/public/favicon/apple-touch-icon.png and /dev/null differ diff --git a/apps/docs/public/favicon/archive/android-chrome-192x192.png b/apps/docs/public/favicon/archive/android-chrome-192x192.png new file mode 100644 index 00000000000..c0ac2bb1cdb Binary files /dev/null and b/apps/docs/public/favicon/archive/android-chrome-192x192.png differ diff --git a/apps/docs/public/favicon/archive/android-chrome-512x512.png b/apps/docs/public/favicon/archive/android-chrome-512x512.png new file mode 100644 index 00000000000..4de626fcc9e Binary files /dev/null and b/apps/docs/public/favicon/archive/android-chrome-512x512.png differ diff --git a/apps/docs/public/favicon/archive/apple-touch-icon-114x114.png b/apps/docs/public/favicon/archive/apple-touch-icon-114x114.png new file mode 100644 index 00000000000..4aa46d4d25d Binary files /dev/null and b/apps/docs/public/favicon/archive/apple-touch-icon-114x114.png differ diff --git a/apps/docs/public/favicon/archive/apple-touch-icon-120x120.png b/apps/docs/public/favicon/archive/apple-touch-icon-120x120.png new file mode 100644 index 00000000000..d2b0e50ef41 Binary files /dev/null and b/apps/docs/public/favicon/archive/apple-touch-icon-120x120.png differ diff --git a/apps/docs/public/favicon/archive/apple-touch-icon-144x144.png b/apps/docs/public/favicon/archive/apple-touch-icon-144x144.png new file mode 100644 index 00000000000..00a5d8b5180 Binary files /dev/null and b/apps/docs/public/favicon/archive/apple-touch-icon-144x144.png differ diff --git a/apps/docs/public/favicon/archive/apple-touch-icon-152x152.png b/apps/docs/public/favicon/archive/apple-touch-icon-152x152.png new file mode 100644 index 00000000000..310532c4648 Binary files /dev/null and b/apps/docs/public/favicon/archive/apple-touch-icon-152x152.png differ diff --git a/apps/docs/public/favicon/archive/apple-touch-icon-57x57.png b/apps/docs/public/favicon/archive/apple-touch-icon-57x57.png new file mode 100644 index 00000000000..1314e02c744 Binary files /dev/null and b/apps/docs/public/favicon/archive/apple-touch-icon-57x57.png differ diff --git a/apps/docs/public/favicon/archive/apple-touch-icon-60x60.png b/apps/docs/public/favicon/archive/apple-touch-icon-60x60.png new file mode 100644 index 00000000000..0615675e809 Binary files /dev/null and b/apps/docs/public/favicon/archive/apple-touch-icon-60x60.png differ diff --git a/apps/docs/public/favicon/archive/apple-touch-icon-72x72.png b/apps/docs/public/favicon/archive/apple-touch-icon-72x72.png new file mode 100644 index 00000000000..e4418058cef Binary files /dev/null and b/apps/docs/public/favicon/archive/apple-touch-icon-72x72.png differ diff --git a/apps/docs/public/favicon/archive/apple-touch-icon-76x76.png b/apps/docs/public/favicon/archive/apple-touch-icon-76x76.png new file mode 100644 index 00000000000..9a76327f94b Binary files /dev/null and b/apps/docs/public/favicon/archive/apple-touch-icon-76x76.png differ diff --git a/apps/docs/public/favicon/archive/favicon-128.png b/apps/docs/public/favicon/archive/favicon-128.png new file mode 100644 index 00000000000..d5dca345802 Binary files /dev/null and b/apps/docs/public/favicon/archive/favicon-128.png differ diff --git a/apps/docs/public/favicon/archive/favicon-16x16.png b/apps/docs/public/favicon/archive/favicon-16x16.png new file mode 100644 index 00000000000..6361bcecd15 Binary files /dev/null and b/apps/docs/public/favicon/archive/favicon-16x16.png differ diff --git a/apps/docs/public/favicon/archive/favicon-196x196.png b/apps/docs/public/favicon/archive/favicon-196x196.png new file mode 100644 index 00000000000..adb05b43453 Binary files /dev/null and b/apps/docs/public/favicon/archive/favicon-196x196.png differ diff --git a/apps/docs/public/favicon/archive/favicon-32x32.png b/apps/docs/public/favicon/archive/favicon-32x32.png new file mode 100644 index 00000000000..4ea0fba2e3c Binary files /dev/null and b/apps/docs/public/favicon/archive/favicon-32x32.png differ diff --git a/apps/docs/public/favicon/archive/favicon-96x96.png b/apps/docs/public/favicon/archive/favicon-96x96.png new file mode 100644 index 00000000000..92a3d735dff Binary files /dev/null and b/apps/docs/public/favicon/archive/favicon-96x96.png differ diff --git a/apps/docs/public/favicon/archive/favicon.ico b/apps/docs/public/favicon/archive/favicon.ico new file mode 100644 index 00000000000..4e808093c05 Binary files /dev/null and b/apps/docs/public/favicon/archive/favicon.ico differ diff --git a/apps/docs/public/favicon/archive/mstile-144x144.png b/apps/docs/public/favicon/archive/mstile-144x144.png new file mode 100644 index 00000000000..00a5d8b5180 Binary files /dev/null and b/apps/docs/public/favicon/archive/mstile-144x144.png differ diff --git a/apps/docs/public/favicon/archive/mstile-150x150.png b/apps/docs/public/favicon/archive/mstile-150x150.png new file mode 100644 index 00000000000..4de1d9b1cc1 Binary files /dev/null and b/apps/docs/public/favicon/archive/mstile-150x150.png differ diff --git a/apps/docs/public/favicon/archive/mstile-310x150.png b/apps/docs/public/favicon/archive/mstile-310x150.png new file mode 100644 index 00000000000..b1121df62aa Binary files /dev/null and b/apps/docs/public/favicon/archive/mstile-310x150.png differ diff --git a/apps/docs/public/favicon/archive/mstile-310x310.png b/apps/docs/public/favicon/archive/mstile-310x310.png new file mode 100644 index 00000000000..7ea95ae09c0 Binary files /dev/null and b/apps/docs/public/favicon/archive/mstile-310x310.png differ diff --git a/apps/docs/public/favicon/archive/mstile-70x70.png b/apps/docs/public/favicon/archive/mstile-70x70.png new file mode 100644 index 00000000000..d5dca345802 Binary files /dev/null and b/apps/docs/public/favicon/archive/mstile-70x70.png differ diff --git a/apps/docs/public/favicon/favicon-128.png b/apps/docs/public/favicon/favicon-128.png new file mode 100644 index 00000000000..2206096f21b Binary files /dev/null and b/apps/docs/public/favicon/favicon-128.png differ diff --git a/apps/docs/public/favicon/favicon-16x16.png b/apps/docs/public/favicon/favicon-16x16.png index 4b7e28143e7..2e07b15e106 100644 Binary files a/apps/docs/public/favicon/favicon-16x16.png and b/apps/docs/public/favicon/favicon-16x16.png differ diff --git a/apps/docs/public/favicon/favicon-196x196.png b/apps/docs/public/favicon/favicon-196x196.png new file mode 100644 index 00000000000..d10fc7e36d9 Binary files /dev/null and b/apps/docs/public/favicon/favicon-196x196.png differ diff --git a/apps/docs/public/favicon/favicon-32x32.png b/apps/docs/public/favicon/favicon-32x32.png index 1a268444bf9..4101a08499b 100644 Binary files a/apps/docs/public/favicon/favicon-32x32.png and b/apps/docs/public/favicon/favicon-32x32.png differ diff --git a/apps/docs/public/favicon/favicon-96x96.png b/apps/docs/public/favicon/favicon-96x96.png new file mode 100644 index 00000000000..b23072a2c02 Binary files /dev/null and b/apps/docs/public/favicon/favicon-96x96.png differ diff --git a/apps/docs/public/favicon/favicon.ico b/apps/docs/public/favicon/favicon.ico index 5c4d1dcc5cf..51b9988fdcb 100644 Binary files a/apps/docs/public/favicon/favicon.ico and b/apps/docs/public/favicon/favicon.ico differ diff --git a/apps/docs/public/favicon/mstile-144x144.png b/apps/docs/public/favicon/mstile-144x144.png new file mode 100644 index 00000000000..c73891c985d Binary files /dev/null and b/apps/docs/public/favicon/mstile-144x144.png differ diff --git a/apps/docs/public/favicon/mstile-150x150.png b/apps/docs/public/favicon/mstile-150x150.png index 836aed416a0..bd10ae0a5df 100644 Binary files a/apps/docs/public/favicon/mstile-150x150.png and b/apps/docs/public/favicon/mstile-150x150.png differ diff --git a/apps/docs/public/favicon/mstile-310x150.png b/apps/docs/public/favicon/mstile-310x150.png new file mode 100644 index 00000000000..fb4d58022df Binary files /dev/null and b/apps/docs/public/favicon/mstile-310x150.png differ diff --git a/apps/docs/public/favicon/mstile-310x310.png b/apps/docs/public/favicon/mstile-310x310.png new file mode 100644 index 00000000000..0adc45ea0ac Binary files /dev/null and b/apps/docs/public/favicon/mstile-310x310.png differ diff --git a/apps/docs/public/favicon/mstile-70x70.png b/apps/docs/public/favicon/mstile-70x70.png new file mode 100644 index 00000000000..2206096f21b Binary files /dev/null and b/apps/docs/public/favicon/mstile-70x70.png differ diff --git a/apps/docs/public/favicon/safari-pinned-tab.svg b/apps/docs/public/favicon/safari-pinned-tab.svg deleted file mode 100644 index 7e408790eb0..00000000000 --- a/apps/docs/public/favicon/safari-pinned-tab.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - -Created by potrace 1.11, written by Peter Selinger 2001-2013 - - - - - diff --git a/apps/docs/public/favicon/site.webmanifest b/apps/docs/public/favicon/site.webmanifest index 8da2b0f3d44..c72ae95e89d 100644 --- a/apps/docs/public/favicon/site.webmanifest +++ b/apps/docs/public/favicon/site.webmanifest @@ -3,12 +3,12 @@ "short_name": "Supabase", "icons": [ { - "src": "/favicons/android-chrome-192x192.png", + "src": "/favicon/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { - "src": "/favicons/android-chrome-512x512.png", + "src": "/favicon/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index a87525bceb6..6407c1bb7a2 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -11,6 +11,7 @@ Ariuna K Beng Eu Bo Lu Bobbie Soedirgo +Chandana Anumula Chase Granberry Chris Copplestone Dave Wilson @@ -50,6 +51,7 @@ Qiao Han Ramiro Nuñez Dosio Rodrigo Martins Mansueli Rory Wilding +Sreyas Udayavarman Stanislav M Steve Chavez Stojan Dimitrovski diff --git a/apps/docs/public/img/guides/resources/migrating-to-supabase/amazon-rds/amazon-rds_credentials.png b/apps/docs/public/img/guides/resources/migrating-to-supabase/amazon-rds/amazon-rds_credentials.png new file mode 100644 index 00000000000..9a692329b15 Binary files /dev/null and b/apps/docs/public/img/guides/resources/migrating-to-supabase/amazon-rds/amazon-rds_credentials.png differ diff --git a/apps/docs/public/img/guides/resources/migrating-to-supabase/amazon-rds/supabase_dashboard.png b/apps/docs/public/img/guides/resources/migrating-to-supabase/amazon-rds/supabase_dashboard.png new file mode 100644 index 00000000000..3a1ff5b9daf Binary files /dev/null and b/apps/docs/public/img/guides/resources/migrating-to-supabase/amazon-rds/supabase_dashboard.png differ diff --git a/apps/docs/public/img/icons/aws-rds-icon.svg b/apps/docs/public/img/icons/aws-rds-icon.svg new file mode 100644 index 00000000000..37e7661d465 --- /dev/null +++ b/apps/docs/public/img/icons/aws-rds-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/docs/public/img/icons/menu/reference-csharp.svg b/apps/docs/public/img/icons/menu/reference-csharp.svg index 40d0375079d..84c1dcf8966 100644 --- a/apps/docs/public/img/icons/menu/reference-csharp.svg +++ b/apps/docs/public/img/icons/menu/reference-csharp.svg @@ -1,9 +1,9 @@ - - - - - - - - + + + + + + + + diff --git a/apps/docs/public/img/icons/menu/reference-swift-light.svg b/apps/docs/public/img/icons/menu/reference-swift-light.svg index d11ab08bb58..149f5459b15 100644 --- a/apps/docs/public/img/icons/menu/reference-swift-light.svg +++ b/apps/docs/public/img/icons/menu/reference-swift-light.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/apps/docs/public/img/icons/menu/reference-swift.svg b/apps/docs/public/img/icons/menu/reference-swift.svg index d11ab08bb58..10bec817613 100644 --- a/apps/docs/public/img/icons/menu/reference-swift.svg +++ b/apps/docs/public/img/icons/menu/reference-swift.svg @@ -1,9 +1 @@ - - - - - - - - - + \ No newline at end of file diff --git a/apps/docs/public/img/icons/openai_logo-light.svg b/apps/docs/public/img/icons/openai_logo-light.svg new file mode 100644 index 00000000000..3b4eff961f3 --- /dev/null +++ b/apps/docs/public/img/icons/openai_logo-light.svg @@ -0,0 +1,2 @@ + +OpenAI icon \ No newline at end of file diff --git a/apps/docs/public/img/icons/openai_logo.svg b/apps/docs/public/img/icons/openai_logo.svg new file mode 100644 index 00000000000..9aacd2a1115 --- /dev/null +++ b/apps/docs/public/img/icons/openai_logo.svg @@ -0,0 +1,2 @@ + +OpenAI icon \ No newline at end of file diff --git a/apps/docs/public/img/redwoodjs-qs-connection-strings-pool.png b/apps/docs/public/img/redwoodjs-qs-connection-strings-pool.png new file mode 100644 index 00000000000..3718cd797e3 Binary files /dev/null and b/apps/docs/public/img/redwoodjs-qs-connection-strings-pool.png differ diff --git a/apps/docs/public/img/redwoodjs-qs-countries-ui.png b/apps/docs/public/img/redwoodjs-qs-countries-ui.png new file mode 100644 index 00000000000..50e98e5630c Binary files /dev/null and b/apps/docs/public/img/redwoodjs-qs-countries-ui.png differ diff --git a/apps/docs/public/img/redwoodjs-qs-new-project.png b/apps/docs/public/img/redwoodjs-qs-new-project.png new file mode 100644 index 00000000000..46cc1555991 Binary files /dev/null and b/apps/docs/public/img/redwoodjs-qs-new-project.png differ diff --git a/apps/docs/public/img/redwoodjs-qs-splash.png b/apps/docs/public/img/redwoodjs-qs-splash.png new file mode 100644 index 00000000000..750e5ad68f3 Binary files /dev/null and b/apps/docs/public/img/redwoodjs-qs-splash.png differ diff --git a/apps/docs/styles/main.scss b/apps/docs/styles/main.scss index ea1f9b78324..dedeb72ffd9 100644 --- a/apps/docs/styles/main.scss +++ b/apps/docs/styles/main.scss @@ -23,7 +23,7 @@ } body { - @apply bg-scale-100 dark:bg-[#1c1c1c] #{!important}; + @apply bg-scale-200 dark:bg-[#1c1c1c] #{!important}; scroll-behavior: smooth; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; diff --git a/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx b/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx index 85b061947c7..e0b1c949f8c 100644 --- a/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx +++ b/apps/www/_blog/2023-02-07-chatgpt-supabase-docs.mdx @@ -114,3 +114,7 @@ While you're at it, consider supporting [@ankane](https://github.com/ankane)'s g Want to try Supabase Clippy? It's a hidden feature while in MVP - visit [supabase.com/docs](/docs) and hit `cmd` + `/` to pull up the search box (sorry mobile users). If you have any comments we'd love to hear them in our [ShowHN](https://news.ycombinator.com/item?id=34695306). + +## Deploy your own + +Want to build your own Clippy based on your own content? Use our [Next.js OpenAI Doc Search Template](https://github.com/supabase-community/nextjs-openai-doc-search) to deploy your own now! diff --git a/apps/www/_blog/2023-04-04-infinite-scroll-with-nextjs-framer-motion.mdx b/apps/www/_blog/2023-04-04-infinite-scroll-with-nextjs-framer-motion.mdx index 406cc058b93..a670abe9610 100644 --- a/apps/www/_blog/2023-04-04-infinite-scroll-with-nextjs-framer-motion.mdx +++ b/apps/www/_blog/2023-04-04-infinite-scroll-with-nextjs-framer-motion.mdx @@ -4,6 +4,7 @@ description: Lazy load and paginate data on scroll with Next.js and a sprinkle o author: fsansalvadore image: infinite-scroll-thumb.jpg thumb: infinite-scroll-thumb.jpg +launchweek: 7 tags: - tutorial - launch-week diff --git a/apps/www/_blog/2023-04-07-designing-with-ai-midjourney.mdx b/apps/www/_blog/2023-04-07-designing-with-ai-midjourney.mdx new file mode 100644 index 00000000000..2a82e547dce --- /dev/null +++ b/apps/www/_blog/2023-04-07-designing-with-ai-midjourney.mdx @@ -0,0 +1,292 @@ +--- +title: 'Designing with AI: Generating unique artwork for every user' +description: Using MidJourney to generative artwork and serving ticket images with Edge Functions +author: marijana,thor_schaeff +image: launch-week-7/designing-with-ai-midjourney/header.png +thumb: launch-week-7/designing-with-ai-midjourney/header.png +launchweek: 7 +tags: + - launch-week +date: '2023-04-07' +toc_depth: 6 +--- + +While Supabase Launch Weeks happen online, we love the idea of bringing everyone together as if it was an in-person conference. Our ticketing system is one of the ways that we emulate this. + +We have been issuing tickets since Launch Week 5, and they were so successful that we have done them ever since. It’s become a challenge to outdo the previous effort with something better every time. + +## Prompts with Midjourney + +You've probably heard of this "AI" tech, and most likely tired of the low-effort SkyNet memes. We wanted to do something more integrated with our developer/designer workflow. Something that we could share with developers & designers that they might find useful. + +[Midjourney](https://www.midjourney.com/) was where we started for art generation. We experimented with prompts, blends, and colors to create eye-catching visuals for each ticket connected to a GitHub profile. Some first attempts and ideas included prompts like: + +``` +/imagine abstract 3 layers of waves on a dark background with light refracting through it, very cool subtle minimal dark illustration, purple light leak with subtle highlights +``` + +In our exploration of different commands and prompts, we found that the `/blend` command was particularly enjoyable and helpful for creating the desired visuals. This command allowed us to quickly upload multiple images and experiment with different combinations and aesthetics to create a cohesive new image. For designers, the `/blend` command is like `/imagine` but visual, making it easy to visually experiment with different elements. Experiments included blurred edges, planetoid shapes, light leaks, shadows, swirls, motion blurs, waves, cubes, reflections, bokeh, and lots and lots of _“make it look really really blurred”_. + +![midjoruney experiments](/images/blog/launch-week-7/designing-with-ai-midjourney/experiments.jpg) + +After exploring a variety of text prompts, blends, and styles, we decided that most of the visuals we generated were too chaotic and busy for our Launch Week tickets. Images felt cluttered and overwhelming, making it difficult to read overlaid text. We shifted our focus to creating a cleaner, simpler aesthetic. This required a selective approach to the visual components we wanted to include. + +After what-seemed-like hours (okay, just a few), we were able to generate some interesting artwork that could be useful. We chose purple hues and simpler swirls - minimalist visuals that could serve as backgrounds for our tickets and landing page. + +![midjourney variations](/images/blog/launch-week-7/designing-with-ai-midjourney/midjourney-variations.png) + +### Chasing Gold Behind the Scenes + +When we were happy with the main visual for Launch Week 7, we also needed to create gold visuals for the extra special tickets, and those needed to look the same… but gold. Initially, we thought we could simply use the prompt `/imagine *seed number* make it gold` to generate the desired effect. To our surprise, MidJourney did not cooperate with this idea: + +![midjourney failed attempts.jpg](/images/blog/launch-week-7/designing-with-ai-midjourney/failed-attempts.jpg) + +We tried excluding certain elements, such as faces or circles to refine the output, and at one point every prompt ended with a `-- no faces, -- no circles, -- no gold bars ...`, but the occasional gold bars still appeared. + +We needed to come up with a different approach (even though this approach seemed more fun). + +Eventually, we just added a golden overlay gradient to previously generated images and then used `/blend` to blend the purple and the gold together. + +And voila, we had a baseline for generating variations: + +![midjourney blending variations](/images/blog/launch-week-7/designing-with-ai-midjourney/midjourney-blending-variations.png) + +The final visual that you see everywhere won us over because of its vibrant swirls. While each swirl looks slightly different, a consistent style is still maintained throughout. + +Fortunately, we didn't require full-width images for each ticket. Upscaled MidJourney images sufficed. Nevertheless, we had to recreate the main visual on the landing page in vectors to use it effectively. You can view it in full glory at [https://supabase.com/launch-week](https://supabase.com/launch-week). + +![Screenshot 2023-04-06 at 09.47.47.png](/images/blog/launch-week-7/designing-with-ai-midjourney/launch-week-7-ticket-home.png) + +## Open Graph images + +An important aspect of Launch Week tickets is their shareability. Each Launch Week we’ve been blown away by how many developers post their tickets on social channels (and we absolutely love seeing them!). + +When you share your unique ticket URL, the image shown on the social preview is called an Open Graph image (or OG image for short). + +These images are generated for each unique URL and ticket. This requires a bit of magic, which in our case means using a [Supabase Edge Function](https://supabase.com/docs/guides/functions) together with [Supabase Storage](https://supabase.com/docs/guides/storage) for smart caching. + +### Supabase Edge Function 🤝 Supabase Storage + +Our Edge Function handles the generation of each ticket image, and also does a bunch of other things under the hood, like detecting if the ticket was shared on socials. This will become important later! + +```jsx +if (userAgent?.toLocaleLowerCase().includes('twitter')) { + // Attendee shared on Twitter + await supabaseAdminClient + .from('lw7_tickets') + .update({ sharedOnTwitter: 'now' }) + .eq('username', username) + .is('sharedOnTwitter', null) +} else if (userAgent?.toLocaleLowerCase().includes('linkedin')) { + // Attendee shared on LinkedIn + await supabaseAdminClient + .from('lw7_tickets') + .update({ sharedOnLinkedIn: 'now' }) + .eq('username', username) + .is('sharedOnLinkedIn', null) +} +``` + +We want to be as efficient as possible because generating a png file in an edge function is an expensive operation. We generate each ticket only once and then save it to Supabase Storage (which has a [smart CDN cache built in](https://supabase.com/blog/storage-image-resizing-smart-cdn#smart-cdn-deep-dive)). + +So in the first step we check if we can fetch the user’s image from storage: + +```tsx +// Try to get image from Supabase Storage CDN. +storageResponse = await fetch( + `${STORAGE_URL}/tickets/regular/${BUCKET_FOLDER_VERSION}/${username}.png` +) +``` + +If we can’t find the image in storage, then we kick off the ticket generation pipeline, using Vercel’s awesome [open-source satori library](https://github.com/vercel/satori) transforms HTML & CSS into svgs! + +Each image includes the user’s GitHub details. We use [`supabase-js`](https://supabase.com/docs/reference/javascript/installing) for authentication: users log in with their GitHub account and we store their username in a table in Postgres. + +Since this table includes email addresses, we secure it using [RLS](https://supabase.com/docs/guides/auth/row-level-security) to ensure each user can only view their own data. At the same time, we want these tickets to be publicly shareable, and that’s where [Postgres Views](https://supabase.com/blog/postgresql-views) come in handy. + +By creating a view, we can selectively publicize parts of our table and also compute some additional values on the fly: + +```sql +drop + view if exists lw7_tickets_golden; + +create +or replace view lw7_tickets_golden as +with + lw7_referrals as ( + select + referred_by, + count(*) as referrals + from + lw7_tickets + where + referred_by is not null + group by + referred_by + ) +select + lw7_tickets."id", + lw7_tickets."name", + lw7_tickets."username", + lw7_tickets."ticketNumber", + lw7_tickets."createdAt", + lw7_tickets."sharedOnTwitter", + lw7_tickets."sharedOnLinkedIn", + lw7_tickets."bg_image_id", + case + when lw7_referrals.referrals is null then 0 + ELSE lw7_referrals.referrals + end as referrals, + case + when lw7_tickets."sharedOnTwitter" is not null + and lw7_tickets."sharedOnLinkedIn" is not null then true + else false + end as golden +from + lw7_tickets + left outer join lw7_referrals on lw7_tickets.username = lw7_referrals.referred_by; + +select + * +from + lw7_tickets_golden +``` + +We can now retrieve that username by using the following code: + +```jsx +// Get ticket data +const { data, error } = await supabaseAdminClient + .from('lw7_tickets_golden') + .select('name, ticketNumber, golden, bg_image_id') + .eq('username', username) + .maybeSingle() +if (error) console.log(error.message) +if (!data) throw new Error('user not found') +const { name, ticketNumber, bg_image_id } = data +const golden = data?.golden ?? false +``` + +You can now probably guess why our edge function was tracking requests from the Twitter and LinkedIn bots! That’s exactly the condition used to turn your ticket golden. How cool is that, with the power of Postgres, we can do all of this within the Database, absolutely mind-blowing. Also, we can easily track a referral count. Relational DBs for the win! + +With our public view in place, we can now easily retrieve the relevant ticket details needed to generate the image, via `supabase-js`: + +The ticket image itself is just a layering of some background images, your GitHub profile picture, and some text elements, et voila you’ve got yourself a unique ticket image! + +
+
+ ![Main background image](/images/blog/launch-week-7/designing-with-ai-midjourney/ticket-backdrop.png) +
+ Main background image +
+
+ +
+ ![Ticket outline](/images/blog/launch-week-7/designing-with-ai-midjourney/ticket-outline.png) +
Ticket outline
+
+ +
+ ![Random AI generated background for the ticket + outline](/images/blog/launch-week-7/designing-with-ai-midjourney/ticket-ai-background.png) +
Random AI generated background for the ticket outline
+
+ +
+ ![Layer it all together and put some nice text on top and you got yourself a beautiful + ticket!](/images/blog/launch-week-7/designing-with-ai-midjourney/ticket-details.png) +
Layer it all together and put some nice text on top and you got yourself a beautiful ticket!
+
+
+ +Once generated, we can conveniently upload the image to Supabase Storage using `supabase-js`. This ensures fast response times as well as efficient resource usage. + +```jsx +const type = golden ? 'golden' : 'regular' + +// Upload image to storage. +const { error: storageError } = await supabaseAdminClient.storage + .from('images') + .upload(`lw7/tickets/${type}/${BUCKET_FOLDER_VERSION}/${username}.png`, generatedImage.body, { + contentType: 'image/png', + cacheControl: '31536000', + upsert: false, + }) +``` + +And of course, all of this is open source, you can find the full function code [here](https://github.com/supabase/supabase/tree/master/apps/www/supabase/functions/lw7-ticket-og). Please feel free to utilize it for your own Launches! + +### Turning tickets golden in realtime + +Using the power of the entire Supabase stack, we’ve designed a pretty neat mechanic to allow users to turn their tickets golden. + +In previous Launch Weeks, we employed the [fibonacci sequence](https://en.wikipedia.org/wiki/Fibonacci_sequence) to sprinkle golden tickets around using the ticket number sequence. This time around we wanted to make it more interactive and allow the user to earn their golden ticket, increasing their chance to win swag. + +Remember the Twitter and LinkedIn bot detection from above? We use those to generate the `golden` column in our public view: + +```sql +case + when lw7_tickets."sharedOnTwitter" is not null + and lw7_tickets."sharedOnLinkedIn" is not null then true + else false +end as golden +``` + +As folks are sharing their tickets on socials to earn their gold status, we also want to give them realtime feedback on their progress. Luckily we also have a feature for that, [Supabase Realtime](https://supabase.com/realtime). + +Something that would have been a headache in the past is just a couple of lines of client-side JavaScript: + +```jsx +const channel = supabase + .channel('changes') + .on( + 'postgres_changes', + { + event: 'UPDATE', + schema: 'public', + table: 'lw7_tickets', + filter: `username=eq.${username}`, + }, + (payload) => { + const golden = !!payload.new.sharedOnTwitter && !!payload.new.sharedOnLinkedIn + setUserData({ + ...payload.new, + golden, + }) + if (golden) { + channel.unsubscribe() + } + } + ) + .subscribe() +``` + +Interested to know how this fits within your Next.js application? Find the code [here](https://github.com/supabase/supabase/blob/master/apps/www/components/LaunchWeek/Ticket/TicketForm.tsx#L70-L102). + +## Displaying the images + +We have some images, so how can we now display them somewhere? [@Francesco Sansalvadore](https://twitter.com/frnk_snslvdr) saw one of our team members trying to “swipe” on the tickets slider component on the [launch week page](https://supabase.com/launch-week/tickets) and he thought, why not feature all the fantastic people who generated tickets on a single page? + +We built a [ticket wall](https://supabase.com/launch-week/tickets) that you can scroll endlessly. We approached it with an infinite scroll technique, lazy-loading a few tickets at a time. + +![launch week 7 ticket wall](/images/blog/launch-week-7/designing-with-ai-midjourney/launch-week-7-ticket-wall.png) + +If you’re interested in a more detailed step-by-step guide to reproduce this effect, take a look at the [Infinite scroll with Next.js, Framer Motion, and Supabase](/blog/infinite-scroll-with-nextjs-framer-motion) blog post. + +## Get your ticket + +You too can also be Charlie Bucket and have a Golden Ticket. There is no chocolate factory, however, but we do have some amazing swag to win. + +Up for grabs are: + +- Supabase mechanical keyboard. In fact, we have 3 of them to give away!\_ Guaranteed to annoy your co-workers/cat/partner/ yourself. +- Socks: _Perfect for your `