From 5b3b0553d6f28681770c3e00fae209e8436ae677 Mon Sep 17 00:00:00 2001 From: Ollie Halliday <22655069+Hallidayo@users.noreply.github.com> Date: Tue, 15 Nov 2022 16:13:50 +0000 Subject: [PATCH 001/215] changed the get user js code snippet --- studio/components/to-be-cleaned/Docs/Snippets.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/to-be-cleaned/Docs/Snippets.js b/studio/components/to-be-cleaned/Docs/Snippets.js index 937780e7059..77e09510960 100644 --- a/studio/components/to-be-cleaned/Docs/Snippets.js +++ b/studio/components/to-be-cleaned/Docs/Snippets.js @@ -660,7 +660,7 @@ curl -X GET '${endpoint}/auth/v1/user' \\ js: { language: 'js', code: ` -const user = supabase.auth.user() +const { data: { user } } = await supabase.auth.getUser() `, }, }), From 9e82c045084c0848c28e402cba734a54a350cd14 Mon Sep 17 00:00:00 2001 From: gitstart Date: Wed, 16 Nov 2022 12:36:37 +0000 Subject: [PATCH 002/215] chore: Hide the settings page on self-hosted / CLI --- studio/next.config.js | 228 +++++++++++++++++++++++------------------- 1 file changed, 127 insertions(+), 101 deletions(-) diff --git a/studio/next.config.js b/studio/next.config.js index 0684e797af3..3ac852ca38c 100644 --- a/studio/next.config.js +++ b/studio/next.config.js @@ -12,105 +12,132 @@ const withTM = require('next-transpile-modules')(['ui', 'common']) // https://nextjs.org/docs/api-reference/next.config.js/introduction // https://docs.sentry.io/platforms/javascript/guides/nextjs/ +const IS_PLATFORM = process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' + +// Fixes the issue with the user having direct URL access to these pages in non-platform environments, +// by redirecting to the respective available page. +const nonPlatformRedirects = [ + { + source: '/project/:ref/auth/providers', + destination: '/project/:ref/auth/users', + permanent: true, + }, + { + source: '/project/:ref/auth/templates', + destination: '/project/:ref/auth/users', + permanent: true, + }, + { + source: '/project/:ref/auth/url-configuration', + destination: '/project/:ref/auth/users', + permanent: true, + }, +] + +const generalRedirects = [ + { + source: '/', + destination: '/sign-in', + permanent: false, + }, + { + source: '/register', + destination: '/sign-up', + permanent: false, + }, + { + source: '/signup', + destination: '/sign-up', + permanent: false, + }, + { + source: '/signin', + destination: '/sign-in', + permanent: false, + }, + { + source: '/login', + destination: '/sign-in', + permanent: false, + }, + { + source: '/log-in', + destination: '/sign-in', + permanent: false, + }, + { + source: '/project/:ref/auth', + destination: '/project/:ref/auth/users', + permanent: true, + }, + { + source: '/project/:ref/database', + destination: '/project/:ref/database/tables', + permanent: true, + }, + { + source: '/project/:ref/storage', + destination: '/project/:ref/storage/buckets', + permanent: true, + }, + { + source: '/project/:ref/settings', + destination: '/project/:ref/settings/general', + permanent: true, + }, + { + source: '/project/:ref/auth/settings', + destination: '/project/:ref/auth/users', + permanent: true, + }, + { + source: '/project/:ref/settings/billing', + destination: '/project/:ref/settings/billing/subscription', + permanent: true, + }, + { + source: '/project/:ref/database/postgres-logs', + destination: '/project/:ref/logs/postgres-logs', + permanent: true, + }, + { + source: '/project/:ref/database/postgrest-logs', + destination: '/project/:ref/logs/postgrest-logs', + permanent: true, + }, + { + source: '/project/:ref/database/pgbouncer-logs', + destination: '/project/:ref/logs/pgbouncer-logs', + permanent: true, + }, + { + source: '/project/:ref/database/realtime-logs', + destination: '/project/:ref/logs/realtime-logs', + permanent: true, + }, + { + source: '/project/:ref/storage/logs', + destination: '/project/:ref/logs/storage-logs', + permanent: true, + }, + { + source: '/project/:ref/auth/logs', + destination: '/project/:ref/logs/auth-logs', + permanent: true, + }, + { + source: '/project/:ref/logs-explorer', + destination: '/project/:ref/logs/explorer', + permanent: true, + }, +] + const nextConfig = { async redirects() { - return [ - { - source: '/', - destination: '/sign-in', - permanent: false, - }, - { - source: '/register', - destination: '/sign-up', - permanent: false, - }, - { - source: '/signup', - destination: '/sign-up', - permanent: false, - }, - { - source: '/signin', - destination: '/sign-in', - permanent: false, - }, - { - source: '/login', - destination: '/sign-in', - permanent: false, - }, - { - source: '/log-in', - destination: '/sign-in', - permanent: false, - }, - { - source: '/project/:ref/auth', - destination: '/project/:ref/auth/users', - permanent: true, - }, - { - source: '/project/:ref/database', - destination: '/project/:ref/database/tables', - permanent: true, - }, - { - source: '/project/:ref/storage', - destination: '/project/:ref/storage/buckets', - permanent: true, - }, - { - source: '/project/:ref/settings', - destination: '/project/:ref/settings/general', - permanent: true, - }, - { - source: '/project/:ref/auth/settings', - destination: '/project/:ref/auth/users', - permanent: true, - }, - { - source: '/project/:ref/settings/billing', - destination: '/project/:ref/settings/billing/subscription', - permanent: true, - }, - { - source: '/project/:ref/database/postgres-logs', - destination: '/project/:ref/logs/postgres-logs', - permanent: true, - }, - { - source: '/project/:ref/database/postgrest-logs', - destination: '/project/:ref/logs/postgrest-logs', - permanent: true, - }, - { - source: '/project/:ref/database/pgbouncer-logs', - destination: '/project/:ref/logs/pgbouncer-logs', - permanent: true, - }, - { - source: '/project/:ref/database/realtime-logs', - destination: '/project/:ref/logs/realtime-logs', - permanent: true, - }, - { - source: '/project/:ref/storage/logs', - destination: '/project/:ref/logs/storage-logs', - permanent: true, - }, - { - source: '/project/:ref/auth/logs', - destination: '/project/:ref/logs/auth-logs', - permanent: true, - }, - { - source: '/project/:ref/logs-explorer', - destination: '/project/:ref/logs/explorer', - permanent: true, - }, - ] + if (!IS_PLATFORM) { + return [...nonPlatformRedirects, ...generalRedirects] + } + return generalRedirects }, async headers() { return [ @@ -162,7 +189,6 @@ const sentryWebpackPluginOptions = { // Make sure adding Sentry options is the last code to run before exporting, to // ensure that your source maps include changes from all other Webpack plugins -module.exports = - process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' - ? withSentryConfig(moduleExports, sentryWebpackPluginOptions) - : withPlugins([withTM()], nextConfig) +module.exports = IS_PLATFORM + ? withSentryConfig(moduleExports, sentryWebpackPluginOptions) + : withPlugins([withTM()], nextConfig) From 3dbdc58ef8652f1478ce567a6405dcde4dbcc35a Mon Sep 17 00:00:00 2001 From: gitstart Date: Thu, 17 Nov 2022 06:21:20 +0000 Subject: [PATCH 003/215] chore: disables permanent route caching --- studio/next.config.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/studio/next.config.js b/studio/next.config.js index 3ac852ca38c..fa3c8265c0a 100644 --- a/studio/next.config.js +++ b/studio/next.config.js @@ -20,17 +20,17 @@ const nonPlatformRedirects = [ { source: '/project/:ref/auth/providers', destination: '/project/:ref/auth/users', - permanent: true, + permanent: false, }, { source: '/project/:ref/auth/templates', destination: '/project/:ref/auth/users', - permanent: true, + permanent: false, }, { source: '/project/:ref/auth/url-configuration', destination: '/project/:ref/auth/users', - permanent: true, + permanent: false, }, ] From ec50e28c927b3d1a87d61e2ac1b9e2a70d226762 Mon Sep 17 00:00:00 2001 From: gitstart Date: Thu, 24 Nov 2022 05:34:36 +0000 Subject: [PATCH 004/215] chore: updates page banning logic --- studio/hooks/misc/withAuth.tsx | 14 +- studio/next.config.js | 228 +++++++++++++++------------------ 2 files changed, 112 insertions(+), 130 deletions(-) diff --git a/studio/hooks/misc/withAuth.tsx b/studio/hooks/misc/withAuth.tsx index bd0bfc2e287..4ce144da02b 100644 --- a/studio/hooks/misc/withAuth.tsx +++ b/studio/hooks/misc/withAuth.tsx @@ -8,7 +8,13 @@ import { useProfile, useStore, usePermissions } from 'hooks' import Error500 from '../../pages/500' import { NextPageWithLayout } from 'types' -const PLATFORM_ONLY_PAGES = ['reports', 'settings'] +const PLATFORM_ONLY_PAGES = [ + 'reports', + 'settings', + 'auth/providers', + 'auth/templates', + 'auth/url-configuration', +] export function withAuth( WrappedComponent: ComponentType | NextPageWithLayout, @@ -23,7 +29,7 @@ export function withAuth( const { ref, slug } = router.query const { app, ui } = rootStore - const page = router.pathname.split('/')[3] + const page = router.pathname.split('/').slice(3).join('/') const redirectTo = options?.redirectTo ?? defaultRedirectTo(ref) const redirectIfFound = options?.redirectIfFound @@ -37,7 +43,9 @@ export function withAuth( mutate: mutatePermissions, } = usePermissions(profile, returning) - const isAccessingBlockedPage = !IS_PLATFORM && PLATFORM_ONLY_PAGES.includes(page) + const isAccessingBlockedPage = + !IS_PLATFORM && + PLATFORM_ONLY_PAGES.some((platformOnlyPage) => page.startsWith(platformOnlyPage)) const isRedirecting = isAccessingBlockedPage || checkRedirectTo(isLoading, router, profile, error, redirectTo, redirectIfFound) diff --git a/studio/next.config.js b/studio/next.config.js index fa3c8265c0a..0684e797af3 100644 --- a/studio/next.config.js +++ b/studio/next.config.js @@ -12,132 +12,105 @@ const withTM = require('next-transpile-modules')(['ui', 'common']) // https://nextjs.org/docs/api-reference/next.config.js/introduction // https://docs.sentry.io/platforms/javascript/guides/nextjs/ -const IS_PLATFORM = process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' - -// Fixes the issue with the user having direct URL access to these pages in non-platform environments, -// by redirecting to the respective available page. -const nonPlatformRedirects = [ - { - source: '/project/:ref/auth/providers', - destination: '/project/:ref/auth/users', - permanent: false, - }, - { - source: '/project/:ref/auth/templates', - destination: '/project/:ref/auth/users', - permanent: false, - }, - { - source: '/project/:ref/auth/url-configuration', - destination: '/project/:ref/auth/users', - permanent: false, - }, -] - -const generalRedirects = [ - { - source: '/', - destination: '/sign-in', - permanent: false, - }, - { - source: '/register', - destination: '/sign-up', - permanent: false, - }, - { - source: '/signup', - destination: '/sign-up', - permanent: false, - }, - { - source: '/signin', - destination: '/sign-in', - permanent: false, - }, - { - source: '/login', - destination: '/sign-in', - permanent: false, - }, - { - source: '/log-in', - destination: '/sign-in', - permanent: false, - }, - { - source: '/project/:ref/auth', - destination: '/project/:ref/auth/users', - permanent: true, - }, - { - source: '/project/:ref/database', - destination: '/project/:ref/database/tables', - permanent: true, - }, - { - source: '/project/:ref/storage', - destination: '/project/:ref/storage/buckets', - permanent: true, - }, - { - source: '/project/:ref/settings', - destination: '/project/:ref/settings/general', - permanent: true, - }, - { - source: '/project/:ref/auth/settings', - destination: '/project/:ref/auth/users', - permanent: true, - }, - { - source: '/project/:ref/settings/billing', - destination: '/project/:ref/settings/billing/subscription', - permanent: true, - }, - { - source: '/project/:ref/database/postgres-logs', - destination: '/project/:ref/logs/postgres-logs', - permanent: true, - }, - { - source: '/project/:ref/database/postgrest-logs', - destination: '/project/:ref/logs/postgrest-logs', - permanent: true, - }, - { - source: '/project/:ref/database/pgbouncer-logs', - destination: '/project/:ref/logs/pgbouncer-logs', - permanent: true, - }, - { - source: '/project/:ref/database/realtime-logs', - destination: '/project/:ref/logs/realtime-logs', - permanent: true, - }, - { - source: '/project/:ref/storage/logs', - destination: '/project/:ref/logs/storage-logs', - permanent: true, - }, - { - source: '/project/:ref/auth/logs', - destination: '/project/:ref/logs/auth-logs', - permanent: true, - }, - { - source: '/project/:ref/logs-explorer', - destination: '/project/:ref/logs/explorer', - permanent: true, - }, -] - const nextConfig = { async redirects() { - if (!IS_PLATFORM) { - return [...nonPlatformRedirects, ...generalRedirects] - } - return generalRedirects + return [ + { + source: '/', + destination: '/sign-in', + permanent: false, + }, + { + source: '/register', + destination: '/sign-up', + permanent: false, + }, + { + source: '/signup', + destination: '/sign-up', + permanent: false, + }, + { + source: '/signin', + destination: '/sign-in', + permanent: false, + }, + { + source: '/login', + destination: '/sign-in', + permanent: false, + }, + { + source: '/log-in', + destination: '/sign-in', + permanent: false, + }, + { + source: '/project/:ref/auth', + destination: '/project/:ref/auth/users', + permanent: true, + }, + { + source: '/project/:ref/database', + destination: '/project/:ref/database/tables', + permanent: true, + }, + { + source: '/project/:ref/storage', + destination: '/project/:ref/storage/buckets', + permanent: true, + }, + { + source: '/project/:ref/settings', + destination: '/project/:ref/settings/general', + permanent: true, + }, + { + source: '/project/:ref/auth/settings', + destination: '/project/:ref/auth/users', + permanent: true, + }, + { + source: '/project/:ref/settings/billing', + destination: '/project/:ref/settings/billing/subscription', + permanent: true, + }, + { + source: '/project/:ref/database/postgres-logs', + destination: '/project/:ref/logs/postgres-logs', + permanent: true, + }, + { + source: '/project/:ref/database/postgrest-logs', + destination: '/project/:ref/logs/postgrest-logs', + permanent: true, + }, + { + source: '/project/:ref/database/pgbouncer-logs', + destination: '/project/:ref/logs/pgbouncer-logs', + permanent: true, + }, + { + source: '/project/:ref/database/realtime-logs', + destination: '/project/:ref/logs/realtime-logs', + permanent: true, + }, + { + source: '/project/:ref/storage/logs', + destination: '/project/:ref/logs/storage-logs', + permanent: true, + }, + { + source: '/project/:ref/auth/logs', + destination: '/project/:ref/logs/auth-logs', + permanent: true, + }, + { + source: '/project/:ref/logs-explorer', + destination: '/project/:ref/logs/explorer', + permanent: true, + }, + ] }, async headers() { return [ @@ -189,6 +162,7 @@ const sentryWebpackPluginOptions = { // Make sure adding Sentry options is the last code to run before exporting, to // ensure that your source maps include changes from all other Webpack plugins -module.exports = IS_PLATFORM - ? withSentryConfig(moduleExports, sentryWebpackPluginOptions) - : withPlugins([withTM()], nextConfig) +module.exports = + process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' + ? withSentryConfig(moduleExports, sentryWebpackPluginOptions) + : withPlugins([withTM()], nextConfig) From faf9252253c5c4223da616456bd9146a000d3459 Mon Sep 17 00:00:00 2001 From: gitstart Date: Wed, 7 Dec 2022 08:18:06 +0000 Subject: [PATCH 005/215] fix: row click event trigger on menu item click --- .../to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.js | 4 ++++ studio/localStores/storageExplorer/StorageExplorerStore.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.js index ebf400f1ea1..efc6aa48d98 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerRow.js @@ -307,6 +307,10 @@ const FileExplorerRow = ({ className={`flex items-center justify-end ${ view === STORAGE_VIEWS.LIST ? 'flex-grow' : 'w-[10%]' }`} + onClick={(event) => + // Stops click event from this div, to resolve an issue with menu item's click event triggering unexpected row select + event.stopPropagation() + } > {item.status === STORAGE_ROW_STATUS.LOADING ? ( Date: Wed, 14 Dec 2022 01:39:57 +0800 Subject: [PATCH 006/215] start using react.memo to stop re-renders --- .../Navigation/NavigationMenu/HomeMenu.tsx | 10 ++--- .../NavigationMenu/NavigationMenu.tsx | 37 +++++++++++++------ .../NavigationMenuGuideList.tsx | 23 +++++------- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx index a769976a8a4..8101690b2f4 100644 --- a/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx @@ -1,8 +1,7 @@ +import { useTheme } from 'common/Providers' import Image from 'next/image' import Link from 'next/link' import { useRouter } from 'next/router' -import { useMenuLevelId } from '~/hooks/useMenuState' -import { useTheme } from 'common/Providers' const home = [ [ @@ -106,8 +105,7 @@ const home = [ ], ] -const NavigationMenuHome = () => { - const level = useMenuLevelId() +const NavigationMenuHome = ({ active }) => { const router = useRouter() const { isDarkMode } = useTheme() @@ -115,9 +113,7 @@ const NavigationMenuHome = () => {
    diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx index 5a945b965c5..e997936f5f2 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx @@ -1,6 +1,6 @@ import { useRouter } from 'next/router' import { memo, useEffect } from 'react' -import { menuState } from '~/hooks/useMenuState' +import { menuState, useMenuLevelId } from '~/hooks/useMenuState' import NavigationMenuGuideList from './NavigationMenuGuideList' import NavigationMenuRefList from './NavigationMenuRefList' // @ts-expect-error @@ -153,20 +153,33 @@ const SideNav = () => { } }, [router.events]) + const level = useMenuLevelId() + + const isHomeActive = 'home' === level + const isGettingStartedActive = 'gettingstarted' === level + const isDatabaseActive = 'database' === level + const isFunctionsActive = 'functions' === level + const isRealtimeActive = 'realtime' === level + const isStorageActive = 'storage' === level + const isPlatformActive = 'platform' === level + const isResourcesActive = 'resources' === level + const isIntegrationsActive = 'integrations' === level + const isReferenceActive = 'reference' === level + return (
    {/* // main menu */} - - - - - - - - - - - + + + + + + + + + + + {/* // Client Libs */} = ({ id, setMenuLevelId }) => { +const NavigationMenuGuideList: React.FC = ({ id, active }) => { const router = useRouter() const { isDarkMode } = useTheme() const menu = NavItems[id] - //console.log(id, 'url is:', menu.url) - // get url const url = router.asPath // remove the end of the url if a deep link const firstLevelRoute = url?.split('/')?.slice(0, 4)?.join('/') - const level = useMenuLevelId() - return ( = ({ id, setMenuLevelId }) => { className={[ 'transition-all ml-8 duration-150 ease-out', // enabled - level === id && 'opacity-100 ml-0 delay-150', - level === 'home' && 'ml-12', + active && 'opacity-100 ml-0 delay-150', + // level === 'home' && 'ml-12', // disabled - level !== 'home' && level !== id ? '-ml-8' : '', - level !== id ? 'opacity-0 invisible absolute' : '', + // level !== 'home' && level !== id ? '-ml-8' : '', + active ? 'opacity-0 invisible absolute' : '', ].join(' ')} >
      @@ -83,7 +78,7 @@ const NavigationMenuGuideList: React.FC = ({ id, setMenuLevelId }) => { : 'hover:text-brand-900 text-scale-1200', ].join(' ')} > - {menu.title ?? level} + {menu.title ?? id}
    @@ -228,4 +223,4 @@ const NavigationMenuGuideList: React.FC = ({ id, setMenuLevelId }) => { ) } -export default NavigationMenuGuideList +export default React.memo(NavigationMenuGuideList) From 948132c4068d6039a8d7b2eeacf79e6461ffc0a2 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Wed, 14 Dec 2022 01:47:41 +0800 Subject: [PATCH 007/215] do same with red docs --- .../NavigationMenu/NavigationMenu.tsx | 22 ++++++++++++++++++- .../NavigationMenuGuideList.tsx | 2 +- .../NavigationMenu/NavigationMenuRefList.tsx | 10 +++++---- apps/docs/hooks/useMenuState.ts | 2 +- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx index e997936f5f2..db10c1d02b0 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx @@ -158,6 +158,7 @@ const SideNav = () => { const isHomeActive = 'home' === level const isGettingStartedActive = 'gettingstarted' === level const isDatabaseActive = 'database' === level + const isAuthActive = 'auth' === level const isFunctionsActive = 'functions' === level const isRealtimeActive = 'realtime' === level const isStorageActive = 'storage' === level @@ -166,13 +167,23 @@ const SideNav = () => { const isIntegrationsActive = 'integrations' === level const isReferenceActive = 'reference' === level + const isReference_Javascript_V1 = 'reference_javascript_v1' === level + const isReference_Javascript_V2 = 'reference_javascript_v2' === level + const isReference_Dart_V0 = 'reference_dart_v0' === level + const isReference_Dart_V1 = 'reference_dart_v1' === level + const isReference_Cli = 'reference_cli' === level + const isReference_Api = 'reference_api' === level + const isReference_Self_Hosting_Auth = 'reference_self_hosting_auth' === level + const isReference_Self_Hosting_Storage = 'reference_self_hosting_storage' === level + const isReference_Self_Hosting_Realtime = 'reference_self_hosting_realtime' === level + return (
    {/* // main menu */} - + @@ -184,6 +195,7 @@ const SideNav = () => { { { { { @@ -226,18 +243,21 @@ const SideNav = () => { diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx index 2180e345af8..d680ecc3dcb 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx @@ -36,7 +36,7 @@ const NavigationMenuGuideList: React.FC = ({ id, active }) => { // disabled // level !== 'home' && level !== id ? '-ml-8' : '', - active ? 'opacity-0 invisible absolute' : '', + !active ? 'opacity-0 invisible absolute' : '', ].join(' ')} >
      diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefList.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefList.tsx index e2db35f487a..908c55ee475 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefList.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefList.tsx @@ -80,6 +80,7 @@ interface INavigationMenuRefList { // the keys of menu items that are allowed to be shown on the side menu // if undefined, we show all the menu items allowedClientKeys?: string[] + active: boolean } const NavigationMenuRefList: React.FC = ({ @@ -87,6 +88,7 @@ const NavigationMenuRefList: React.FC = ({ lib, commonSections, allowedClientKeys, + active, }) => { const router = useRouter() const { isDarkMode } = useTheme() @@ -150,12 +152,12 @@ const NavigationMenuRefList: React.FC = ({ className={[ 'transition-all ml-8 duration-150 ease-out', // enabled - level === id && 'opacity-100 ml-0 delay-150 h-auto', + active && 'opacity-100 ml-0 delay-150 h-auto', // move menu back to margin-left - level === 'home' && 'ml-12', + // level === 'home' && 'ml-12', // disabled - level !== 'home' && level !== id ? '-ml-8' : '', - level !== id ? 'opacity-0 invisible absolute h-0 overflow-hidden' : '', + // level !== 'home' && level !== id ? '-ml-8' : '', + !active ? 'opacity-0 invisible absolute h-0 overflow-hidden' : '', ].join(' ')} >
      diff --git a/apps/docs/hooks/useMenuState.ts b/apps/docs/hooks/useMenuState.ts index 760796e1244..52977307542 100644 --- a/apps/docs/hooks/useMenuState.ts +++ b/apps/docs/hooks/useMenuState.ts @@ -7,7 +7,7 @@ export const menuState = proxy({ setMenuActiveRefId: (value) => { menuState.menuActiveRefId = value }, - menuLevelId: '', + menuLevelId: 'home', setMenuLevelId: (value) => { menuState.menuMobileOpen = false menuState.menuLevelId = value From 8812985ec43059bd56108e0f3c19b72429f08fc6 Mon Sep 17 00:00:00 2001 From: Ollie Halliday <22655069+Hallidayo@users.noreply.github.com> Date: Wed, 14 Dec 2022 14:13:24 +0000 Subject: [PATCH 008/215] added in example description --- apps/docs/components/reference/RefFunctionSection.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/docs/components/reference/RefFunctionSection.tsx b/apps/docs/components/reference/RefFunctionSection.tsx index a31c52af992..18c753a52ef 100644 --- a/apps/docs/components/reference/RefFunctionSection.tsx +++ b/apps/docs/components/reference/RefFunctionSection.tsx @@ -203,6 +203,16 @@ const RefFunctionSection: React.FC = (props) => { .replace('js', '') .replace('ts', ''))} + + {example.description && ( +
      +
      Example Notes
      + + {example.description} + +
      + )} + {response && ( Date: Thu, 15 Dec 2022 01:13:32 +0800 Subject: [PATCH 009/215] more optimizing --- .../NavigationMenu/NavigationMenu.tsx | 32 +- .../NavigationMenuGuideList.tsx | 192 +---------- .../NavigationMenuGuideListItems.tsx | 201 ++++++++++++ .../NavigationMenu/NavigationMenuRefList.tsx | 263 +-------------- .../NavigationMenuRefListItems.tsx | 299 ++++++++++++++++++ apps/docs/layouts/SiteLayout.tsx | 12 +- 6 files changed, 538 insertions(+), 461 deletions(-) create mode 100644 apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideListItems.tsx create mode 100644 apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx index db10c1d02b0..e40f2e99e53 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx @@ -192,14 +192,14 @@ const SideNav = () => { {/* // Client Libs */} - + /> */} { lib="javascript" allowedClientKeys={generateAllowedClientLibKeys(libCommonSections, spec_js_v2)} /> - - */} + {/* + /> */} {/* // Tools */} - - */} + {/* + /> */} {/* // Self Hosting Server */} - - */} + {/* - */} + {/* + /> */}
      ) } diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx index d680ecc3dcb..2800ff25557 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx @@ -1,11 +1,7 @@ -import { useTheme } from 'common/Providers' -import Image from 'next/image' -import Link from 'next/link' +import * as Accordion from '@radix-ui/react-accordion' import { useRouter } from 'next/router' import React from 'react' -import { IconChevronLeft } from '~/../../packages/ui' -import * as NavItems from './NavigationMenu.constants' -import * as Accordion from '@radix-ui/react-accordion' +import NavigationMenuGuideListItems from './NavigationMenuGuideListItems' interface Props { id: string @@ -13,9 +9,6 @@ interface Props { } const NavigationMenuGuideList: React.FC = ({ id, active }) => { const router = useRouter() - const { isDarkMode } = useTheme() - - const menu = NavItems[id] // get url const url = router.asPath @@ -39,186 +32,7 @@ const NavigationMenuGuideList: React.FC = ({ id, active }) => { !active ? 'opacity-0 invisible absolute' : '', ].join(' ')} > -
    - - - - {menu.items.map((x, index) => { - // console.log('1st type of link?', x.items && x.items.length > 0) - // console.log() - return ( -
    - {x.items && x.items.length > 0 ? ( -
    - {x.items.map((subItem, subItemIndex) => { - // console.log('subitem', { subItem }) - // console.log('router', router) - //console.log('subitem url', subItem.url) - let subItemMenuOpen = false - - if (router.asPath.includes(subItem.url)) { - subItemMenuOpen = true - } - - const LinkContainer = (props) => { - return ( - - {props.children} - - ) - } - - return ( - <> - {subItemIndex === 0 && ( - <> -
    - - {x.name} - - - )} - -
  • - - {subItem.icon && ( - {subItem.name - )} - {subItem.name} - -
  • - - {subItem.items && subItem.items.length > 0 && ( - - {subItem.items.map((subSubItem, subSubItemIndex) => { - return ( -
  • - - - {subSubItem.name} - - -
  • - ) - })} -
    - )} -
    - - ) - })} -
    - ) : ( - <> -
  • - - - {x.icon && } - {x.name} - - -
  • - - )} -
    - ) - })} - {menu.extras && ( - <> -
    - - Resources - - - )} - {menu.extras?.map((x) => { - return ( - - ) - })} -
+ ) } diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideListItems.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideListItems.tsx new file mode 100644 index 00000000000..ebcc68d1a75 --- /dev/null +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideListItems.tsx @@ -0,0 +1,201 @@ +import { useTheme } from 'common/Providers' +import Image from 'next/image' +import Link from 'next/link' +import { useRouter } from 'next/router' +import React from 'react' +import { IconChevronLeft } from '~/../../packages/ui' +import * as NavItems from './NavigationMenu.constants' +import * as Accordion from '@radix-ui/react-accordion' + +const Content = ({ id }) => { + const router = useRouter() + const { isDarkMode } = useTheme() + + const menu = NavItems[id] + + return ( +
+ + + + {menu.items.map((x, index) => { + // console.log('1st type of link?', x.items && x.items.length > 0) + // console.log() + return ( +
+ {x.items && x.items.length > 0 ? ( +
+ {x.items.map((subItem, subItemIndex) => { + // console.log('subitem', { subItem }) + // console.log('router', router) + //console.log('subitem url', subItem.url) + let subItemMenuOpen = false + + if (router.asPath.includes(subItem.url)) { + subItemMenuOpen = true + } + + const LinkContainer = (props) => { + return ( + + {props.children} + + ) + } + + return ( + <> + {subItemIndex === 0 && ( + <> +
+ + {x.name} + + + )} + +
  • + + {subItem.icon && ( + {subItem.name + )} + {subItem.name} + +
  • + + {subItem.items && subItem.items.length > 0 && ( + + {subItem.items.map((subSubItem, subSubItemIndex) => { + return ( +
  • + + + {subSubItem.name} + + +
  • + ) + })} +
    + )} +
    + + ) + })} +
    + ) : ( + <> +
  • + + + {x.icon && } + {x.name} + + +
  • + + )} +
    + ) + })} + {menu.extras && ( + <> +
    + + Resources + + + )} + {menu.extras?.map((x) => { + return ( + + ) + })} + + ) +} + +export default React.memo(Content) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefList.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefList.tsx index 908c55ee475..b1ae0480dfd 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefList.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefList.tsx @@ -13,64 +13,9 @@ import { useTheme } from 'common/Providers' import RevVersionDropdown from '~/components/RefVersionDropdown' import { useMenuActiveRefId, useMenuLevelId } from '~/hooks/useMenuState' import { RefIdOptions, RefKeyOptions } from './NavigationMenu' +import NavigationMenuRefListItems from './NavigationMenuRefListItems' -const FunctionLink = ({ - title, - id, - icon, - library, - slug, -}: { - title: string - name?: string - id: string - icon?: string - product?: string - library: string - slug: string -}) => { - const router = useRouter() - const activeAccordianItem = useMenuActiveRefId() - - // check if we're on a versioned page - let version = '' - if (router.asPath.includes('v1')) { - version = 'v1' - } - - if (router.asPath.includes('v0')) { - version = 'v0' - } - - const active = activeAccordianItem === id - return ( -
  • - - - {icon && {icon}} - {title} - - -
  • - ) -} - -const SideMenuTitle = ({ title }: { title: string }) => { - return ( - - {title} - - ) -} - -const Divider = () => { - return
    -} +import React from 'react' interface INavigationMenuRefList { id: RefIdOptions @@ -90,62 +35,10 @@ const NavigationMenuRefList: React.FC = ({ allowedClientKeys, active, }) => { - const router = useRouter() - const { isDarkMode } = useTheme() - - let sections = commonSections - - const allowedKeys = allowedClientKeys - - if (!sections) console.error('no common sections imported') - - const menu = NavItems[id] - - const databaseFunctions = find(sections, { title: 'Database' }) - ? find(sections, { title: 'Database' }).items - : [] - - const authFunctions = find(sections, { title: 'Auth' }) - ? find(sections, { title: 'Auth' }).items - : [] - - const filterIds = - databaseFunctions.length > 0 - ? find(databaseFunctions, { - id: 'using-filters', - }) && - find(databaseFunctions, { - id: 'using-filters', - }) - .items.filter((x) => allowedKeys.includes(x.id)) - .map((x) => x.id) - : [] - const modifierIds = - databaseFunctions.length > 0 - ? find(databaseFunctions, { - id: 'using-modifiers', - }) && - find(databaseFunctions, { - id: 'using-modifiers', - }) - .items.filter((x) => allowedKeys.includes(x.id)) - .map((x) => x.id) - : [] - - const authServerIds = - databaseFunctions.length > 0 - ? find(authFunctions, { - id: 'admin-api', - }) && - find(authFunctions, { - id: 'admin-api', - }).items.map((x) => x.id) - : [] - // console.log(filterIds) // console.log(modifierIds) - const level = useMenuLevelId() + // const level = useMenuLevelId() return (
    = ({ !active ? 'opacity-0 invisible absolute h-0 overflow-hidden' : '', ].join(' ')} > -
    - - -
    -
    - -
    -
    - Back to Main Menu -
    - - -
    - {id} - - {menu.title} - - -
    - {/* )} */} - -
      - {sections.map((fn: any, fnIndex) => { - // - // check if the link is allowed to be displayed - function isFuncNotInLibraryOrVersion(id, type) { - if (id && allowedKeys && !allowedKeys.includes(id) && type !== 'markdown') { - /* - * Remove this menu link from side bar, as it does not exist for either - * this language, or for this lib version - * - * */ - return true - } else { - return false - } - } - - // run allow check - if (isFuncNotInLibraryOrVersion(fn.id, fn.type)) { - return <> - } - - const RenderLink = (props) => { - const activeAccordianItem = useMenuActiveRefId() - let active = false - - const isFilter = - filterIds && filterIds.length > 0 && filterIds.includes(activeAccordianItem) - const isModifier = - modifierIds && modifierIds.length > 0 && modifierIds.includes(activeAccordianItem) - const isAuthServer = - authServerIds && - authServerIds.length > 0 && - authServerIds.includes(activeAccordianItem) - - if ( - (isFilter && !isModifier && !isAuthServer && props.id === 'using-filters') || - (activeAccordianItem === 'using-filters' && props.id === 'using-filters') - ) { - active = true - } else if ( - (isModifier && !isFilter && !isAuthServer && props.id === 'using-modifiers') || - (activeAccordianItem === 'using-modifiers' && props.id === 'using-modifiers') - ) { - active = true - } else if ( - (isAuthServer && !isFilter && !isModifier && props.id === 'admin-api') || - (activeAccordianItem === 'admin-api' && props.id === 'admin-api') - ) { - active = true - } else { - active = false - } - - return ( - - - - - {props.items && - props.items - .filter((item) => allowedKeys.includes(item.id)) - .map((item) => { - return - })} - - - - ) - } - - // handle subtitles with subitems - if (!fn.id) { - return ( - <> - - - {fn.items && - fn.items - //.filter((item) => item.libs && item.libs.includes(lib)) - .map((item) => { - // run allow check - if (isFuncNotInLibraryOrVersion(item.id, item.type)) return <> - return - })} - - ) - } else { - // handle normal links - return ( - <> - - {fn.items && - fn.items - //.filter((item) => item.libs.includes(lib)) - .map((item) => )} - - ) - } - })} -
    -
    +
    ) } -export default NavigationMenuRefList +export default React.memo(NavigationMenuRefList) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx new file mode 100644 index 00000000000..bda02438afc --- /dev/null +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx @@ -0,0 +1,299 @@ +import * as Accordion from '@radix-ui/react-accordion' +import Link from 'next/link' +import { useRouter } from 'next/router' +import { IconChevronLeft } from 'ui' +import * as NavItems from './NavigationMenu.constants' + +import { find } from 'lodash' +import Image from 'next/image' +import { useTheme } from 'common/Providers' + +import RevVersionDropdown from '~/components/RefVersionDropdown' +// import { useMenuActiveRefId } from '~/hooks/useMenuState' +import { RefIdOptions, RefKeyOptions } from './NavigationMenu' + +import React from 'react' + +const FunctionLink = ({ + title, + id, + icon, + library, + slug, +}: { + title: string + name?: string + id: string + icon?: string + product?: string + library: string + slug: string +}) => { + const router = useRouter() + const activeAccordianItem = '1' // useMenuActiveRefId() + + // check if we're on a versioned page + let version = '' + if (router.asPath.includes('v1')) { + version = 'v1' + } + + if (router.asPath.includes('v0')) { + version = 'v0' + } + + const active = activeAccordianItem === id + return ( +
  • + + + {icon && {icon}} + {title} + + +
  • + ) +} + +const SideMenuTitle = ({ title }: { title: string }) => { + return ( + + {title} + + ) +} + +const Divider = () => { + return
    +} + +interface INavigationMenuRefList { + id: RefIdOptions + lib: RefKeyOptions + commonSections: any[] // to do type up + + // the keys of menu items that are allowed to be shown on the side menu + // if undefined, we show all the menu items + allowedClientKeys?: string[] +} + +const Content: React.FC = ({ + id, + lib, + commonSections, + allowedClientKeys, +}) => { + const router = useRouter() + const { isDarkMode } = useTheme() + + console.log( + id && 'id changed', + lib && 'lib changed', + commonSections && 'commonSections changed', + allowedClientKeys && 'allowedClientKeys changed' + ) + + let sections = commonSections + + const allowedKeys = allowedClientKeys + + if (!sections) console.error('no common sections imported') + + const menu = NavItems[id] + + const databaseFunctions = find(sections, { title: 'Database' }) + ? find(sections, { title: 'Database' }).items + : [] + + const authFunctions = find(sections, { title: 'Auth' }) + ? find(sections, { title: 'Auth' }).items + : [] + + const filterIds = + databaseFunctions.length > 0 + ? find(databaseFunctions, { + id: 'using-filters', + }) && + find(databaseFunctions, { + id: 'using-filters', + }) + .items.filter((x) => allowedKeys.includes(x.id)) + .map((x) => x.id) + : [] + const modifierIds = + databaseFunctions.length > 0 + ? find(databaseFunctions, { + id: 'using-modifiers', + }) && + find(databaseFunctions, { + id: 'using-modifiers', + }) + .items.filter((x) => allowedKeys.includes(x.id)) + .map((x) => x.id) + : [] + + const authServerIds = + databaseFunctions.length > 0 + ? find(authFunctions, { + id: 'admin-api', + }) && + find(authFunctions, { + id: 'admin-api', + }).items.map((x) => x.id) + : [] + + // console.log(filterIds) + // console.log(modifierIds) + + return ( +
    + + +
    +
    + +
    +
    + Back to Main Menu +
    + + +
    + {id} + + {menu.title} + + +
    + {/* )} */} + +
      + {sections.map((fn: any, fnIndex) => { + // + // check if the link is allowed to be displayed + function isFuncNotInLibraryOrVersion(id, type) { + if (id && allowedKeys && !allowedKeys.includes(id) && type !== 'markdown') { + /* + * Remove this menu link from side bar, as it does not exist for either + * this language, or for this lib version + * + * */ + return true + } else { + return false + } + } + + // run allow check + if (isFuncNotInLibraryOrVersion(fn.id, fn.type)) { + return <> + } + + const RenderLink = (props) => { + const activeAccordianItem = 'using-filters' // useMenuActiveRefId() + let active = false + + const isFilter = + filterIds && filterIds.length > 0 && filterIds.includes(activeAccordianItem) + const isModifier = + modifierIds && modifierIds.length > 0 && modifierIds.includes(activeAccordianItem) + const isAuthServer = + authServerIds && + authServerIds.length > 0 && + authServerIds.includes(activeAccordianItem) + + if ( + (isFilter && !isModifier && !isAuthServer && props.id === 'using-filters') || + (activeAccordianItem === 'using-filters' && props.id === 'using-filters') + ) { + active = true + } else if ( + (isModifier && !isFilter && !isAuthServer && props.id === 'using-modifiers') || + (activeAccordianItem === 'using-modifiers' && props.id === 'using-modifiers') + ) { + active = true + } else if ( + (isAuthServer && !isFilter && !isModifier && props.id === 'admin-api') || + (activeAccordianItem === 'admin-api' && props.id === 'admin-api') + ) { + active = true + } else { + active = false + } + + return ( + + + + + {props.items && + props.items + .filter((item) => allowedKeys.includes(item.id)) + .map((item) => { + return + })} + + + + ) + } + + // handle subtitles with subitems + if (!fn.id) { + return ( + <> + + + {fn.items && + fn.items + //.filter((item) => item.libs && item.libs.includes(lib)) + .map((item) => { + // run allow check + if (isFuncNotInLibraryOrVersion(item.id, item.type)) return <> + return + })} + + ) + } else { + // handle normal links + return ( + <> + + {fn.items && + fn.items + //.filter((item) => item.libs.includes(lib)) + .map((item) => )} + + ) + } + })} +
    +
    + ) +} + +export default React.memo(Content) diff --git a/apps/docs/layouts/SiteLayout.tsx b/apps/docs/layouts/SiteLayout.tsx index c6ef90060d3..43caab8c0e9 100644 --- a/apps/docs/layouts/SiteLayout.tsx +++ b/apps/docs/layouts/SiteLayout.tsx @@ -7,7 +7,11 @@ import TopNavBarRef from '~/components/Navigation/NavigationMenu/TopNavBarRef' import FooterHelpCallout from '~/components/FooterHelpCallout' -import { menuState, useMenuLevelId, useMenuMobileOpen } from '~/hooks/useMenuState' +import { + menuState, + // useMenuLevelId, + // useMenuMobileOpen +} from '~/hooks/useMenuState' import { useEffect } from 'react' const SiteRefLayout = ({ children }) => { @@ -23,8 +27,10 @@ const SiteRefLayout = ({ children }) => { } }, []) - const menuLevel = useMenuLevelId() - const mobileMenuOpen = useMenuMobileOpen() + const menuLevel = 'home' + // useMenuLevelId() + const mobileMenuOpen = false + // useMenuMobileOpen() const levelsData = { home: { From 0b91ccc9f57a2ed0000a416e29db6ed1a6ee2a4d Mon Sep 17 00:00:00 2001 From: Ollie Halliday <22655069+Hallidayo@users.noreply.github.com> Date: Wed, 14 Dec 2022 19:58:30 +0000 Subject: [PATCH 010/215] supabase js v2 yml removed jsx from descriptions --- spec/supabase_js_v2.yml | 3081 +-------------------------------------- 1 file changed, 11 insertions(+), 3070 deletions(-) diff --git a/spec/supabase_js_v2.yml b/spec/supabase_js_v2.yml index 8899420a2ce..0a3a70f5483 100644 --- a/spec/supabase_js_v2.yml +++ b/spec/supabase_js_v2.yml @@ -1049,96 +1049,7 @@ functions: If you're in a situation where your tables are **NOT** directly related, but instead are joined by a _join table_, you can still use the `select()` method to query the related data. The join table needs - to have the foreign keys as part of its composite primary key. - - - - - ```sql - create table - users ( - id int8 primary key, - name text - ); - create table - teams ( - id int8 primary key, - name text - ); - -- join table - create table - users_teams ( - user_id int8 not null references users, - team_id int8 not null references teams, - -- both foreign keys must be part of a composite primary key - primary key (user_id, team_id) - ); - - insert into - users (id, name) - values - (1, 'Kiran'), - (2, 'Evan'); - insert into - teams (id, name) - values - (1, 'Green'), - (2, 'Blue'); - insert into - users_teams (user_id, team_id) - values - (1, 1), - (1, 2), - (2, 2); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .select(` - name, - teams ( - name - ) - `) - ``` - - - - - ```json - { - "data": [ - { - "name": "Kiran", - "teams": [ - { - "name": "Green" - }, - { - "name": "Blue" - } - ] - }, - { - "name": "Evan", - "teams": [ - { - "name": "Blue" - } - ] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - + to have the foreign keys as part of its composite primary key. hideCodeBlock: true - id: query-the-same-foreign-table-multiple-times name: Query the same foreign table multiple times @@ -1198,69 +1109,6 @@ functions: If you need to query the same foreign table twice, use the name of the joined column to identify which join to use. You can also give each column an alias. - - - - - ```sql - create table - users (id int8 primary key, name text); - - create table - messages ( - sender_id int8 not null references users, - receiver_id int8 not null references users, - content text - ); - - insert into - users (id, name) - values - (1, 'Kiran'), - (2, 'Evan'); - - insert into - messages (sender_id, receiver_id, content) - values - (1, 2, 'đź‘‹'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('messages') - .select(` - content, - from:sender_id(name), - to:receiver_id(name) - `) - ``` - - - - - ```json - { - "data": [ - { - "content": "đź‘‹", - "from": { - "name": "Kiran" - }, - "to": { - "name": "Evan" - } - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: filtering-through-foreign-tables name: Filtering through foreign tables @@ -1314,90 +1162,7 @@ functions: description: | If the filter on a foreign table's column is not satisfied, the foreign table returns `[]` or `null` but the parent table is not filtered out. - - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'Germany'), - (2, 'Indonesia'); - insert into - cities (id, country_id, name) - values - (1, 2, 'Bali'), - (2, 1, 'Munich'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('cities') - .select('name, countries(*)') - .eq('countries.name', 'Estonia') - ``` - - - - - ```json - { - "data": [ - { - "name": "Bali", - "countries": null - }, - { - "name": "Munich", - "countries": null - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - - If you want to filter out the parent table rows, use the `!inner` hint: - - - - - ```ts - const { data, error } = await supabase - .from('cities') - .select('name, countries!inner(*)') - .eq('countries.name', 'Estonia') - ``` - - - - - ```json - { - "data": [], - "status": 200, - "statusText": "OK" - } - ``` - - - + If you want to filter out the parent table rows, use the `!inner` hint hideCodeBlock: true - id: querying-with-count-option name: Querying with count option @@ -1430,46 +1195,7 @@ functions: ``` description: | You can get the number of rows by using the - [count](/docs/reference/javascript/select#parameters) option. For - example, to get the table count without returning all rows: - - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { count, error } = await supabase - .from('countries') - .select('*', { count: 'exact', head: true }) - ``` - - - - - ```json - { - "count": 3, - "status": 200, - "statusText": "OK" - } - ``` - - - + [count](/docs/reference/javascript/select#parameters) option. hideCodeBlock: true - id: querying-json-data name: Querying JSON data @@ -1516,55 +1242,6 @@ functions: [JSON](/docs/guides/database/json) columns. Postgres offers some [operators](/docs/guides/database/json#query-the-jsonb-data) for querying JSON data. - - - - - ```sql - create table - users ( - id int8 primary key, - name text, - address jsonb - ); - - insert into - users (id, name, address) - values - (1, 'Avdotya', '{"city":"Saint Petersburg"}'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .select(` - id, name, - address->city - `) - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Avdotya", - "city": "Saint Petersburg" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: insert @@ -1598,63 +1275,6 @@ functions: "statusText": "Created" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - ``` - - - - - ```ts - const { error } = await supabase - .from('countries') - .insert({ id: 1, name: 'Denmark' }) - ``` - - - - - ```json - { - "status": 201, - "statusText": "Created" - } - ``` - - - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Denmark" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: create-a-record-and-return-it @@ -1685,43 +1305,6 @@ functions: "statusText": "Created" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .insert({ id: 1, name: 'Denmark' }) - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Denmark" - } - ], - "status": 201, - "statusText": "Created" - } - ``` - - - hideCodeBlock: true - id: bulk-create name: Bulk create @@ -1756,68 +1339,6 @@ functions: description: | A bulk create operation is handled in a single transaction. If any of the inserts fail, none of the rows are inserted. - - - - - ```sql - create table - countries (id int8 primary key, name text); - ``` - - - - - ```ts - const { error } = await supabase - .from('countries') - .insert([ - { id: 1, name: 'Nepal' }, - { id: 1, name: 'Vietnam' }, - ]) - ``` - - - - - ```json - { - "error": { - "code": "23505", - "details": "Key (id)=(1) already exists.", - "hint": null, - "message": "duplicate key value violates unique constraint \"countries_pkey\"" - }, - "status": 409, - "statusText": "Conflict" - } - ``` - - - - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - ``` - - - - - ```json - { - "data": [], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: update @@ -1853,70 +1374,6 @@ functions: "statusText": "No Content" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Taiwan'); - ``` - - - - - ```ts - const { error } = await supabase - .from('countries') - .update({ name: 'Australia' }) - .eq('id', 1) - ``` - - - - - ```json - { - "status": 204, - "statusText": "No Content" - } - ``` - - - - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Australia" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: update-a-record-and-return-it @@ -1953,49 +1410,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Taiwan'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .update({ name: 'Australia' }) - .eq('id', 1) - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Australia" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: updating-json-data name: Updating JSON data @@ -2047,65 +1461,7 @@ functions: description: | Postgres offers some [operators](/docs/guides/database/json#query-the-jsonb-data) for - working with JSON data. - - - - - ```sql - create table - users ( - id int8 primary key, - name text, - address jsonb - ); - - insert into - users (id, name, address) - values - (1, 'Michael', '{ "postcode": 90210 }'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .update({ - address: { - street: 'Melrose Place', - postcode: 90210 - } - }) - .eq('address->postcode', 90210) - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Michael", - "address": { - "street": "Melrose Place", - "postcode": 90210 - } - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - - Currently, it is only possible to update the entire JSON document. + working with JSON data. Currently, it is only possible to update the entire JSON document. hideCodeBlock: true - id: upsert title: 'Upsert data: upsert()' @@ -2146,48 +1502,6 @@ functions: "statusText": "Created" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .upsert({ id: 1, name: 'Albania' }) - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Albania" - } - ], - "status": 201, - "statusText": "Created" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: bulk-upsert-your-data @@ -2230,55 +1544,6 @@ functions: "statusText": "Created" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .upsert([ - { id: 1, name: 'Albania' }, - { id: 2, name: 'Algeria' }, - ]) - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Albania" - }, - { - "id": 2, - "name": "Algeria" - } - ], - "status": 201, - "statusText": "Created" - } - ``` - - - hideCodeBlock: true - id: upserting-into-tables-with-constraints name: Upserting into tables with constraints @@ -2322,90 +1587,8 @@ functions: (primary key) column to determine conflicts. If there is no existing row with the same `id`, `upsert()` inserts a new row, which will fail in this case as there is already a row with `handle` `"saoirse"`. - - - - - ```sql - create table - users ( - id int8 generated by default as identity primary key, - handle text not null unique, - display_name text - ); - - insert into - users (id, handle, display_name) - values - (1, 'saoirse', null); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .upsert({ id: 42, handle: 'saoirse', display_name: 'Saoirse' }) - .select() - ``` - - - - - ```json - { - "error": { - "code": "23505", - "details": "Key (handle)=(saoirse) already exists.", - "hint": null, - "message": "duplicate key value violates unique constraint \"users_handle_key\"" - }, - "status": 409, - "statusText": "Conflict" - } - ``` - - - - Using the `onConflict` option, you can instruct `upsert()` to use - another column with a unique constraint to determine conflicts: - - - - - ```ts - await supabase - .from('users') - .upsert( - { id: 42, handle: 'saoirse', display_name: 'Saoirse' }, - { onConflict: 'handle' }, - ) - const { data, error } = await supabase - .from('users') - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 42, - "handle": "saoirse", - "display_name": "Saoirse" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - + another column with a unique constraint to determine conflicts. hideCodeBlock: true - id: delete @@ -2446,65 +1629,6 @@ functions: "statusText": "No Content" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Spain'); - ``` - - - - - ```ts - const { error } = await supabase - .from('countries') - .delete() - .eq('id', 1) - ``` - - - - - ```json - { - "status": 204, - "statusText": "No Content" - } - ``` - - - - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - ``` - - - - - ```json - { - "data": [], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true @@ -2542,36 +1666,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create function hello_world() returns text as $$ - select 'Hello world'; - $$ language sql; - ``` - - - - - ```ts - const { data, error } = await supabase.rpc('hello_world') - ``` - - - - - ```json - { - "data": "Hello world", - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: call-a-postgres-function-with-arguments @@ -2595,36 +1689,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create function echo(say text) returns text as $$ - select say; - $$ language sql; - ``` - - - - - ```ts - const { data, error } = await supabase.rpc('echo', { say: 'đź‘‹' }) - ``` - - - - - ```json - { - "data": "đź‘‹", - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: bulk-processing name: Bulk processing @@ -2652,41 +1716,7 @@ functions: } ``` description: | - You can process large payloads by passing in an array as an argument: - - - - - ```sql - create function add_one_each(arr int[]) returns int[] as $$ - select array_agg(n + 1) from unnest(arr) as n; - $$ language sql; - ``` - - - - - ```ts - const { data, error } = await supabase.rpc('add_one_each', { arr: [1, 2, 3] }) - ``` - - - - - ```json - { - "data": [ - 2, - 3, - 4 - ], - "status": 200, - "statusText": "OK" - } - ``` - - - + You can process large payloads by passing in an array as an argument. hideCodeBlock: true - id: call-a-postgres-function-with-filters name: Call a Postgres function with filters @@ -2728,51 +1758,6 @@ functions: Postgres functions that return tables can also be combined with [Filters](/docs/reference/javascript/using-filters) and [Modifiers](/docs/reference/javascript/using-modifiers). - - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'France'), - (2, 'United Kingdom'); - - create function list_stored_countries() returns setof countries as $$ - select * from countries; - $$ language sql; - ``` - - - - - ```ts - const { data, error } = await supabase - .rpc('list_stored_countries') - .eq('id', 1) - .single() - ``` - - - - - ```json - { - "data": { - "id": 1, - "name": "France" - }, - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: using-filters @@ -2900,56 +1885,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - users ( - id int8 primary key, - name text, - address jsonb - ); - - insert into - users (id, name, address) - values - (1, 'Michael', '{ "postcode": 90210 }'), - (2, 'Jane', null); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .select() - .eq('address->postcode', 90210) - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Michael", - "address": { - "postcode": 90210 - } - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - id: filter-foreign-tables name: Filter Foreign Tables code: | @@ -3006,70 +1941,7 @@ functions: ``` description: | You can filter on foreign tables in your `select()` query using dot - notation: - - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'Germany'), - (2, 'Indonesia'); - insert into - cities (id, country_id, name) - values - (1, 2, 'Bali'), - (2, 1, 'Munich'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select(` - name, - cities!inner ( - name - ) - `) - .eq('cities.name', 'Bali') - ``` - - - - - ```json - { - "data": [ - { - "name": "Indonesia", - "cities": [ - { - "name": "Bali" - } - ] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - + notation. ######## TODO CHECK FILTER YAML ERRORS ######## - id: eq title: eq() @@ -3110,50 +1982,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .eq('name', 'Albania') - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "name": "Albania" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: neq @@ -3199,54 +2027,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .neq('name', 'Albania') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Afghanistan" - }, - { - "id": 3, - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: gt @@ -3288,50 +2068,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .gt('id', 2) - ``` - - - - - ```json - { - "data": [ - { - "id": 3, - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: gte @@ -3377,54 +2113,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .gte('id', 2) - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "name": "Albania" - }, - { - "id": 3, - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: lt @@ -3466,50 +2154,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .lt('id', 2) - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Afghanistan" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: lte @@ -3555,54 +2199,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .lte('id', 2) - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Afghanistan" - }, - { - "id": 2, - "name": "Albania" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: like @@ -3633,50 +2229,6 @@ functions: ``` response: | - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .like('name', '%Alba%') - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "name": "Albania" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: ilike @@ -3718,50 +2270,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .like('name', '%alba%') - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "name": "Albania" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: is @@ -3803,81 +2311,10 @@ functions: } ``` description: | - Using the `eq()` filter doesn't work when filtering for `null`: + Using the `eq()` filter doesn't work when filtering for `null`. - - + Instead, you need to use `is()`. - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'null'), - (2, null); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .eq('name', null) - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "null" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - - Instead, you need to use `is()`: - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .is('name', null) - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "name": null - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: in @@ -3923,54 +2360,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .in('name', ['Albania', 'Algeria']) - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "name": "Albania" - }, - { - "id": 3, - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: contains @@ -4014,52 +2403,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - issues ( - id int8 primary key, - title text, - tags text[] - ); - - insert into - issues (id, title, tags) - values - (1, 'Cache invalidation is not working', array['is:open', 'severity:high', 'priority:low']), - (2, 'Use better names', array['is:open', 'severity:low', 'priority:medium']); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('issues') - .select('title') - .contains('tags', ['is:open', 'severity:high']) - ``` - - - - - ```json - { - "data": [ - { - "title": "Cache invalidation is not working" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: on-range-columns @@ -4106,54 +2449,6 @@ functions: types](https://www.postgresql.org/docs/current/rangetypes.html). You can filter on range columns using the string representation of range values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .contains('during', '[2000-01-01 13:00, 2000-01-01 13:30)') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "room_name": "Emerald", - "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: on-jsonb-columns name: On `jsonb` columns @@ -4192,52 +2487,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - users ( - id int8 primary key, - name text, - address jsonb - ); - - insert into - users (id, name, address) - values - (1, 'Michael', '{ "postcode": 90210, "street": "Melrose Place" }'), - (2, 'Jane', '{}'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .select('name') - .contains('address', { postcode: 90210 }) - ``` - - - - - ```json - { - "data": [ - { - "name": "Michael" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: contained-by @@ -4281,52 +2530,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - classes ( - id int8 primary key, - name text, - days text[] - ); - - insert into - classes (id, name, days) - values - (1, 'Chemistry', array['monday', 'friday']), - (2, 'History', array['monday', 'wednesday', 'thursday']); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('classes') - .select('name') - .containedBy('days', ['monday', 'tuesday', 'wednesday', 'friday']) - ``` - - - - - ```json - { - "data": [ - { - "name": "Chemistry" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: on-range-columns @@ -4373,54 +2576,6 @@ functions: types](https://www.postgresql.org/docs/current/rangetypes.html). You can filter on range columns using the string representation of range values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .containedBy('during', '[2000-01-01 00:00, 2000-01-01 23:59)') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "room_name": "Emerald", - "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: on-jsonb-columns name: On `jsonb` columns @@ -4459,52 +2614,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - users ( - id int8 primary key, - name text, - address jsonb - ); - - insert into - users (id, name, address) - values - (1, 'Michael', '{ "postcode": 90210, "street": "Melrose Place" }'), - (2, 'Jane', '{}'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .select('name') - .containedBy('address', {}) - ``` - - - - - ```json - { - "data": [ - { - "name": "Jane" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: range-gt @@ -4555,54 +2664,6 @@ functions: types](https://www.postgresql.org/docs/current/rangetypes.html). You can filter on range columns using the string representation of range values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeGt('during', '[2000-01-02 08:00, 2000-01-02 09:00)') - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "room_name": "Topaz", - "during": "[\"2000-01-02 09:00:00\",\"2000-01-02 10:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true @@ -4654,54 +2715,6 @@ functions: types](https://www.postgresql.org/docs/current/rangetypes.html). You can filter on range columns using the string representation of range values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeGte('during', '[2000-01-02 08:30, 2000-01-02 09:30)') - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "room_name": "Topaz", - "during": "[\"2000-01-02 09:00:00\",\"2000-01-02 10:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true @@ -4753,54 +2766,6 @@ functions: types](https://www.postgresql.org/docs/current/rangetypes.html). You can filter on range columns using the string representation of range values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeLt('during', '[2000-01-01 15:00, 2000-01-01 16:00)') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "room_name": "Emerald", - "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: range-lte @@ -4851,54 +2816,6 @@ functions: types](https://www.postgresql.org/docs/current/rangetypes.html). You can filter on range columns using the string representation of range values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeLte('during', '[2000-01-01 14:00, 2000-01-01 16:00)') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "room_name": "Emerald", - "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true @@ -4950,54 +2867,6 @@ functions: types](https://www.postgresql.org/docs/current/rangetypes.html). You can filter on range columns using the string representation of range values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeAdjacent('during', '[2000-01-01 12:00, 2000-01-01 13:00)') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "room_name": "Emerald", - "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true @@ -5042,52 +2911,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - issues ( - id int8 primary key, - title text, - tags text[] - ); - - insert into - issues (id, title, tags) - values - (1, 'Cache invalidation is not working', array['is:open', 'severity:high', 'priority:low']), - (2, 'Use better names', array['is:open', 'severity:low', 'priority:medium']); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('issues') - .select('title') - .overlaps('tags', ['is:closed', 'severity:high']) - ``` - - - - - ```json - { - "data": [ - { - "title": "Cache invalidation is not working" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: on-range-columns @@ -5134,54 +2957,6 @@ functions: types](https://www.postgresql.org/docs/current/rangetypes.html). You can filter on range columns using the string representation of range values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .overlaps('during', '[2000-01-01 12:45, 2000-01-01 13:15)') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "room_name": "Emerald", - "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true # TODO: schema & result - id: text-search @@ -5288,49 +3063,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .match({ id: 2, name: 'Albania' }) - ``` - - - - - ```json - { - "data": [ - { - "name": "Albania" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: not @@ -5378,49 +3110,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'null'), - (2, null); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .not('name', 'is', null) - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "null" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: or @@ -5471,52 +3160,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .or('id.eq.2,name.eq.Algeria') - ``` - - - - - ```json - { - "data": [ - { - "name": "Albania" - }, - { - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: use-or-with-and @@ -5553,50 +3196,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .or('id.gt.3,and(id.eq.1,name.eq.Afghanistan)') - ``` - - - - - - ```json - { - "data": [ - { - "name": "Afghanistan" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: use-or-on-foreign-tables name: Use `or` on foreign tables @@ -5652,69 +3251,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'Germany'), - (2, 'Indonesia'); - insert into - cities (id, country_id, name) - values - (1, 2, 'Bali'), - (2, 1, 'Munich'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select(` - name, - cities!inner ( - name - ) - `) - .or('country_id.eq.1,name.eq.Beijing', { foreignTable: 'cities' }) - ``` - - - - - ```json - { - "data": [ - { - "name": "Germany", - "cities": [ - { - "name": "Munich" - } - ] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: filter @@ -5763,50 +3299,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .filter('name', 'in', '("Algeria","Japan")') - ``` - - - - - ```json - { - "data": [ - { - "id": 3, - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: on-a-foreign-table @@ -5863,69 +3355,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'Germany'), - (2, 'Indonesia'); - insert into - cities (id, country_id, name) - values - (1, 2, 'Bali'), - (2, 1, 'Munich'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select(` - name, - cities!inner ( - name - ) - `) - .filter('cities.name', 'eq', 'Bali') - ``` - - - - - ```json - { - "data": [ - { - "name": "Indonesia", - "cities": [ - { - "name": "Bali" - } - ] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: using-modifiers title: Using Modifiers @@ -5976,70 +3405,6 @@ functions: "statusText": "Created" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .upsert({ id: 1, name: 'Albania' }) - ``` - - - - - ```json - { - "status": 201, - "statusText": "Created" - } - ``` - - - - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .upsert({ id: 1, name: 'Algeria' }) - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Algeria" - } - ], - "status": 201, - "statusText": "Created" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: order @@ -6089,58 +3454,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .order('name', { ascending: false }) - ``` - - - - - ```json - { - "data": [ - { - "id": 3, - "name": "Algeria" - }, - { - "id": 2, - "name": "Albania" - }, - { - "id": 1, - "name": "Afghanistan" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: on-a-foreign-table @@ -6207,76 +3520,6 @@ functions: description: | Ordering on foreign tables doesn't affect the ordering of the parent table. - - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'United States'), - (2, 'Vanuatu'); - insert into - cities (id, country_id, name) - values - (1, 1, 'Atlanta'), - (2, 1, 'New York City'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select(` - name, - cities ( - name - ) - `) - .order('name', { foreignTable: 'cities', ascending: false }) - ``` - - - - - ```json - { - "data": [ - { - "name": "United States", - "cities": [ - { - "name": "New York City" - }, - { - "name": "Atlanta" - } - ] - }, - { - "name": "Vanuatu", - "cities": [] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: limit @@ -6317,49 +3560,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .limit(1) - ``` - - - - - ```json - { - "data": [ - { - "name": "Afghanistan" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: on-a-foreign-table @@ -6415,68 +3615,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'United States'); - insert into - cities (id, country_id, name) - values - (1, 1, 'Atlanta'), - (2, 1, 'New York City'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select(` - name, - cities ( - name - ) - `) - .limit(1, { foreignTable: 'cities' }) - ``` - - - - - ```json - { - "data": [ - { - "name": "United States", - "cities": [ - { - "name": "Atlanta" - } - ] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true - id: range title: range() @@ -6519,52 +3657,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .range(0, 1) - ``` - - - - - ```json - { - "data": [ - { - "name": "Afghanistan" - }, - { - "name": "Albania" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: abort-signal @@ -6606,38 +3698,8 @@ functions: } ``` description: | - You can use an [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to abort requests. Note that `status` and `statusText` don't mean anything for aborted requests as the request wasn't fulfilled. - - - - - ```ts - const ac = new AbortController() - ac.abort() - const { data, error } = await supabase - .from('very_big_table') - .select() - .abortSignal(ac.signal) - ``` - - - - - ```json - { - "error": { - "message": "FetchError: The user aborted a request.", - "details": "", - "hint": "", - "code": "" - }, - "status": 400, - "statusText": "Bad Request" - } - ``` - - - + You can use an [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to abort requests. + Note that `status` and `statusText` don't mean anything for aborted requests as the request wasn't fulfilled. hideCodeBlock: true isSpotlight: true - id: single @@ -6677,48 +3739,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .limit(1) - .single() - ``` - - - - - ```json - { - "data": { - "name": "Afghanistan" - }, - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: maybe-single @@ -6755,45 +3775,6 @@ functions: "statusText": "OK" } ``` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .eq('name', 'Singapore') - .maybeSingle() - ``` - - - - - ```json - { - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true - id: csv @@ -6832,45 +3813,6 @@ functions: ``` description: | By default, the data is returned in JSON format, but can also be returned as Comma Separated Values. - - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .csv() - ``` - - - - - ```json - { - "data": "id,name\n1,Afghanistan\n2,Albania\n3,Algeria", - "status": 200, - "statusText": "OK" - } - ``` - - - hideCodeBlock: true isSpotlight: true @@ -6969,7 +3911,6 @@ functions: examples: - id: basic-invocation name: Basic invocation - description: isSpotlight: true code: | ```js From 73b0c16057c80dc4ce2805fe56773c1947230fad Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Fri, 16 Dec 2022 10:16:26 -0330 Subject: [PATCH 011/215] Cleanup and add social icons --- .../docs/components/Footer/DarkModeToggle.tsx | 45 --- apps/docs/components/Footer/index.tsx | 133 -------- apps/docs/components/FooterHelpCallout.tsx | 14 +- apps/docs/components/Navigation/Footer.tsx | 31 ++ .../NavigationMenu/TopNavBarRef.tsx | 28 +- .../reference/RefSectionHandler.tsx | 14 +- apps/docs/layouts/Default.tsx | 94 ----- apps/docs/layouts/SiteLayout.tsx | 32 +- apps/docs/layouts/SiteRefLayout.tsx | 321 ------------------ apps/docs/layouts/guides/index.tsx | 2 +- apps/docs/pages/[...slug].tsx | 74 ---- 11 files changed, 60 insertions(+), 728 deletions(-) delete mode 100644 apps/docs/components/Footer/DarkModeToggle.tsx delete mode 100644 apps/docs/components/Footer/index.tsx create mode 100644 apps/docs/components/Navigation/Footer.tsx delete mode 100644 apps/docs/layouts/Default.tsx delete mode 100644 apps/docs/layouts/SiteRefLayout.tsx delete mode 100644 apps/docs/pages/[...slug].tsx diff --git a/apps/docs/components/Footer/DarkModeToggle.tsx b/apps/docs/components/Footer/DarkModeToggle.tsx deleted file mode 100644 index 04f9d225854..00000000000 --- a/apps/docs/components/Footer/DarkModeToggle.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { useTheme } from 'common/Providers' -import { IconSun, IconMoon } from '@supabase/ui' - -function DarkModeToggle() { - const { isDarkMode, toggleTheme } = useTheme() - - const toggleDarkMode = () => { - localStorage.setItem('supabaseDarkMode', (!isDarkMode).toString()) - toggleTheme() - - const key = localStorage.getItem('supabaseDarkMode') - document.documentElement.className = key === 'true' ? 'dark' : '' - } - - return ( -
    - - - -
    - ) -} - -export default DarkModeToggle diff --git a/apps/docs/components/Footer/index.tsx b/apps/docs/components/Footer/index.tsx deleted file mode 100644 index 01424c906f5..00000000000 --- a/apps/docs/components/Footer/index.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import { Badge } from 'ui' -import Link from 'next/link' -import Image from 'next/image' -import FooterLinks from 'data/footer.json' -import DarkModeToggle from './DarkModeToggle' -import { useTheme } from 'common/Providers' -import * as supabaseLogoWordmarkDark from 'common/assets/images/supabase-logo-wordmark--dark.png' -import * as supabaseLogoWordmarkLight from 'common/assets/images/supabase-logo-wordmark--light.png' - -const Footer = () => { - const { isDarkMode } = useTheme() - - return ( - - ) -} - -export default Footer diff --git a/apps/docs/components/FooterHelpCallout.tsx b/apps/docs/components/FooterHelpCallout.tsx index 82f1a03c683..2eb6421dc4c 100644 --- a/apps/docs/components/FooterHelpCallout.tsx +++ b/apps/docs/components/FooterHelpCallout.tsx @@ -1,11 +1,13 @@ const FooterHelpCallout = () => { return ( -
    -
    Need some help?
    -

    - Not to worry, our specialist engineers are here to help. Submit a support ticket through the{' '} - Dashboard. -

    +
    +
    +
    Need some help?
    +

    + Not to worry, our specialist engineers are here to help. Submit a support ticket through + the Dashboard. +

    +
    ) } diff --git a/apps/docs/components/Navigation/Footer.tsx b/apps/docs/components/Navigation/Footer.tsx new file mode 100644 index 00000000000..1c91cef7360 --- /dev/null +++ b/apps/docs/components/Navigation/Footer.tsx @@ -0,0 +1,31 @@ +import Link from 'next/link' + +const Footer = () => { + return ( +
    +
    +
    + Supabase 2022 + — + + Contributing + + + Changelog + + + + Author Styleguide + + + Open Source + + + SupaSquad + +
    +
    + ) +} + +export default Footer diff --git a/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx b/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx index 6fcae17712d..d5a706d6cb2 100644 --- a/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx @@ -2,19 +2,7 @@ import Image from 'next/image' import Link from 'next/link' import { useRouter } from 'next/router' import { FC, useEffect, useState } from 'react' -import { - Badge, - Button, - Dropdown, - IconCommand, - IconMenu, - IconMessageSquare, - IconMoon, - IconSearch, - IconSun, - Input, - Listbox, -} from 'ui' +import { Button, IconCommand, IconGitHub, IconMoon, IconSearch, IconSun } from 'ui' import { SearchButton } from '~/components/DocSearch' import { REFERENCES } from '~/components/Navigation/Navigation.constants' import { useTheme } from 'common/Providers' @@ -134,7 +122,7 @@ const TopNavBarRef: FC = () => { target="_blank" rel="noreferrer noopener" > - Main site + Supabase.com +
      + +
    diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx index b8ea7efeab4..96bb2416bcf 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx @@ -173,7 +173,7 @@ const SideNav = () => { {/* // Client Libs */} { allowedClientKeys={generateAllowedClientLibKeys(libCommonSections, spec_dart_v0)} /> Date: Tue, 20 Dec 2022 17:28:30 -0330 Subject: [PATCH 072/215] Exclude Edit this Page button --- apps/docs/layouts/guides/index.tsx | 30 ++++++++++++++++++------------ apps/docs/pages/index.mdx | 2 +- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/apps/docs/layouts/guides/index.tsx b/apps/docs/layouts/guides/index.tsx index 25b26b3fec7..e3b64828a60 100644 --- a/apps/docs/layouts/guides/index.tsx +++ b/apps/docs/layouts/guides/index.tsx @@ -31,10 +31,11 @@ const Layout: FC = (props) => { const [tocList, setTocList] = useState([]) const { asPath } = useRouter() - const page = getPageType(asPath) const router = useRouter() + const EDIT_BUTTON_EXCLUDE_LIST = ['/404'] + useEffect(() => { if (hash && tocList.length > 0) { highlightSelectedTocItem(hash as string) @@ -100,18 +101,23 @@ const Layout: FC = (props) => { )}
    {props.children} -
    -
    - - - Edit this page on GitHub - - + + {EDIT_BUTTON_EXCLUDE_LIST.includes(router.route) ? ( + <> + ) : ( + -
    + )}
    {!props.hideToc && hasTableOfContents && !props.meta?.hide_table_of_contents && ( diff --git a/apps/docs/pages/index.mdx b/apps/docs/pages/index.mdx index 5a7e2f17b28..9921286f50c 100644 --- a/apps/docs/pages/index.mdx +++ b/apps/docs/pages/index.mdx @@ -30,7 +30,7 @@ export const meta = { ">
    -
    +

    Getting Started

    Discover how to set up a database to an app making queries in just a few minutes. From 8ef8d1ec648ae01f99f0294a7750c26f883cafa0 Mon Sep 17 00:00:00 2001 From: Fabian Buchenberger <37747351+ffabss@users.noreply.github.com> Date: Tue, 20 Dec 2022 21:51:39 +0000 Subject: [PATCH 073/215] fix broken self-host/docker link in documentation --- apps/docs/pages/guides/self-hosting.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/self-hosting.mdx b/apps/docs/pages/guides/self-hosting.mdx index 1c99b9765d9..950432a3803 100644 --- a/apps/docs/pages/guides/self-hosting.mdx +++ b/apps/docs/pages/guides/self-hosting.mdx @@ -149,7 +149,7 @@ We realize that database migrations are difficult, and this is one of the proble While Supabase officially supports Docker, we have several other deployment strategies managed by the community: -- [supabase-docker](../../guides/hosting/docker) (Official) +- [supabase-docker](./docker) (Official) - [supabase-kubernetes](https://github.com/supabase-community/supabase-kubernetes) (Unofficial) - [supabase-terraform](https://github.com/supabase-community/supabase-terraform) (Unofficial) - [supabase-traefik](https://github.com/supabase-community/supabase-traefik) (Unofficial) From f9d46684c4aee6dd4f0b202e2a52e014cef54ef1 Mon Sep 17 00:00:00 2001 From: dng Date: Tue, 20 Dec 2022 14:07:49 -0800 Subject: [PATCH 074/215] Update apps/docs/pages/guides/self-hosting.mdx --- apps/docs/pages/guides/self-hosting.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/self-hosting.mdx b/apps/docs/pages/guides/self-hosting.mdx index 950432a3803..3092dd9cf45 100644 --- a/apps/docs/pages/guides/self-hosting.mdx +++ b/apps/docs/pages/guides/self-hosting.mdx @@ -149,7 +149,7 @@ We realize that database migrations are difficult, and this is one of the proble While Supabase officially supports Docker, we have several other deployment strategies managed by the community: -- [supabase-docker](./docker) (Official) +- [supabase-docker](/docs/guides/self-hosting/docker) (Official) - [supabase-kubernetes](https://github.com/supabase-community/supabase-kubernetes) (Unofficial) - [supabase-terraform](https://github.com/supabase-community/supabase-terraform) (Unofficial) - [supabase-traefik](https://github.com/supabase-community/supabase-traefik) (Unofficial) From b5b1a58d408f7e0935196c3e32a0d1c04bf5e339 Mon Sep 17 00:00:00 2001 From: dng Date: Tue, 20 Dec 2022 14:13:40 -0800 Subject: [PATCH 075/215] Clean up docs callout --- apps/docs/pages/guides/integrations/weweb.mdx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/apps/docs/pages/guides/integrations/weweb.mdx b/apps/docs/pages/guides/integrations/weweb.mdx index 93ddbd5f2bc..c65a695400e 100644 --- a/apps/docs/pages/guides/integrations/weweb.mdx +++ b/apps/docs/pages/guides/integrations/weweb.mdx @@ -39,12 +39,7 @@ Once you have added both to WeWeb, you will have the option to enable realtime t **🚨 Warning 🚨** -> Realtime is disabled by default in Supabase for better database performance and security. Learn more about [realtime functionalities](https://supabase.com/docs/guides/database#realtime) in our documentation. -> Member -> @awalias awalias 16 days ago -> might just wanna change these ones to be relative but otherwise looks great 👍 - -@EWMAs Reply... +> Realtime is disabled by default in Supabase for better database performance and security. Learn more about [realtime functionalities](/docs/guides/realtime). ## Step 2: GET Data from Supabase From 10235c32b210f7261ea6009d350505862c0d1f09 Mon Sep 17 00:00:00 2001 From: dannykng Date: Tue, 20 Dec 2022 14:41:45 -0800 Subject: [PATCH 076/215] Add code block language for syntax highlighting --- .../pages/guides/integrations/authsignal.mdx | 340 ++++++++---------- 1 file changed, 154 insertions(+), 186 deletions(-) diff --git a/apps/docs/pages/guides/integrations/authsignal.mdx b/apps/docs/pages/guides/integrations/authsignal.mdx index 250b822b483..65d83b22d01 100644 --- a/apps/docs/pages/guides/integrations/authsignal.mdx +++ b/apps/docs/pages/guides/integrations/authsignal.mdx @@ -61,7 +61,7 @@ cd supabase-authsignal-example Create a `.env.local` file and enter the following values: -``` +```sh NEXT_PUBLIC_SUPABASE_URL=get-from-supabase-dashboard NEXT_PUBLIC_SUPABASE_ANON_KEY=get-from-supabase-dashboard AUTHSIGNAL_SECRET=get-from-authsignal-dashboard @@ -84,19 +84,19 @@ npm run dev Install the Supabase client and Auth helpers for Next.js: -``` +```bash npm install @supabase/supabase-js @supabase/auth-helpers-nextjs ``` Install the Authsignal Node.js client: -``` +```bash npm install @authsignal/node ``` Finally install 2 packages to help encrypt and serialize session data in cookies: -``` +```bash npm install @hapi/iron cookie npm install --save-dev @types/cookie ``` @@ -105,18 +105,18 @@ npm install --save-dev @types/cookie Add the following code to `/lib/authsignal.ts`: -``` -import { Authsignal } from "@authsignal/node"; +```ts +import { Authsignal } from '@authsignal/node' -const secret = process.env.AUTHSIGNAL_SECRET; +const secret = process.env.AUTHSIGNAL_SECRET if (!secret) { - throw new Error("AUTHSIGNAL_SECRET is undefined"); + throw new Error('AUTHSIGNAL_SECRET is undefined') } -const redirectUrl = "http://localhost:3000/api/callback"; +const redirectUrl = 'http://localhost:3000/api/callback' -export const authsignal = new Authsignal({ secret, redirectUrl }); +export const authsignal = new Authsignal({ secret, redirectUrl }) ``` The `redirectUrl` here is a Next.js API route which Authsignal will redirect back to after an MFA challenge. We'll implement this below. @@ -131,83 +131,72 @@ Next we will add some helper functions for managing cookies: Add the following code to `/lib/cookies.ts`: -``` -import Iron from "@hapi/iron"; -import { Session } from "@supabase/supabase-js"; -import { parse, serialize } from "cookie"; -import { NextApiRequest, NextApiResponse } from "next"; +```ts +import Iron from '@hapi/iron' +import { Session } from '@supabase/supabase-js' +import { parse, serialize } from 'cookie' +import { NextApiRequest, NextApiResponse } from 'next' export async function setTempCookie(session: Session, res: NextApiResponse) { - const token = await Iron.seal(session, TEMP_TOKEN_SECRET, Iron.defaults); + const token = await Iron.seal(session, TEMP_TOKEN_SECRET, Iron.defaults) const cookie = serialize(TEMP_COOKIE, token, { maxAge: session.expires_in, httpOnly: true, - secure: process.env.NODE_ENV === "production", - path: "/", - sameSite: "lax", - }); + secure: process.env.NODE_ENV === 'production', + path: '/', + sameSite: 'lax', + }) - res.setHeader("Set-Cookie", cookie); + res.setHeader('Set-Cookie', cookie) } -export async function getSessionFromTempCookie( - req: NextApiRequest -): Promise { - const cookie = req.headers.cookie as string; +export async function getSessionFromTempCookie(req: NextApiRequest): Promise { + const cookie = req.headers.cookie as string - const cookies = parse(cookie ?? ""); + const cookies = parse(cookie ?? '') - const tempCookie = cookies[TEMP_COOKIE]; + const tempCookie = cookies[TEMP_COOKIE] if (!tempCookie) { - return undefined; + return undefined } - const session = await Iron.unseal( - tempCookie, - TEMP_TOKEN_SECRET, - Iron.defaults - ); + const session = await Iron.unseal(tempCookie, TEMP_TOKEN_SECRET, Iron.defaults) - return session; + return session } export function setAuthCookie(session: Session, res: NextApiResponse) { - const { access_token, refresh_token, expires_in } = session; + const { access_token, refresh_token, expires_in } = session const authCookies = [ { name: ACCESS_TOKEN_COOKIE, value: access_token }, - refresh_token - ? { name: REFRESH_TOKEN_COOKIE, value: refresh_token } - : undefined, + refresh_token ? { name: REFRESH_TOKEN_COOKIE, value: refresh_token } : undefined, ] .filter(isDefined) .map(({ name, value }) => serialize(name, value, { maxAge: expires_in, httpOnly: true, - secure: process.env.NODE_ENV === "production", - path: "/", - sameSite: "lax", + secure: process.env.NODE_ENV === 'production', + path: '/', + sameSite: 'lax', }) - ); + ) // Also clear the temp cookie - const updatedCookies = [ - ...authCookies, - serialize(TEMP_COOKIE, "", { maxAge: -1, path: "/" }), - ]; + const updatedCookies = [...authCookies, serialize(TEMP_COOKIE, '', { maxAge: -1, path: '/' })] - res.setHeader("Set-Cookie", updatedCookies); + res.setHeader('Set-Cookie', updatedCookies) } -const isDefined = (value: T | undefined): value is T => !!value; +const isDefined = (value: T | undefined): value is T => !!value -const TEMP_TOKEN_SECRET = process.env.TEMP_TOKEN_SECRET!; -const TEMP_COOKIE = "as-mfa-cookie"; -const ACCESS_TOKEN_COOKIE = "sb-access-token"; -const REFRESH_TOKEN_COOKIE = "sb-refresh-token"; +const TEMP_TOKEN_SECRET = process.env.TEMP_TOKEN_SECRET! +const TEMP_COOKIE = 'as-mfa-cookie' +const ACCESS_TOKEN_COOKIE = 'sb-access-token' +const REFRESH_TOKEN_COOKIE = 'sb-refresh-token' ``` ## Step 7: Building the UI @@ -216,34 +205,34 @@ We will add some form components for signing in and signing up as well as a basi Add the following code to `/pages/sign-up.tsx`: -``` -import Link from "next/link"; -import { useRouter } from "next/router"; +```ts +import Link from 'next/link' +import { useRouter } from 'next/router' export default function SignUpPage() { - const router = useRouter(); + const router = useRouter() return (
    { - e.preventDefault(); + e.preventDefault() const target = e.target as typeof e.target & { - email: { value: string }; - password: { value: string }; - }; + email: { value: string } + password: { value: string } + } - const email = target.email.value; - const password = target.password.value; + const email = target.email.value + const password = target.password.value - await fetch("/api/sign-up", { - method: "POST", - headers: { "Content-Type": "application/json" }, + await fetch('/api/sign-up', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), - }).then((res) => res.json()); + }).then((res) => res.json()) - router.push("/"); + router.push('/') }} > @@ -253,49 +242,49 @@ export default function SignUpPage() {
    - {"Already have an account? "} + {'Already have an account? '} Sign in
    - ); + ) } ``` Then add the following code to `/pages/sign-in.tsx`: -``` -import Link from "next/link"; -import { useRouter } from "next/router"; +```ts +import Link from 'next/link' +import { useRouter } from 'next/router' export default function SignInPage() { - const router = useRouter(); + const router = useRouter() return (
    { - e.preventDefault(); + e.preventDefault() const target = e.target as typeof e.target & { - email: { value: string }; - password: { value: string }; - }; + email: { value: string } + password: { value: string } + } - const email = target.email.value; - const password = target.password.value; + const email = target.email.value + const password = target.password.value - const { state, mfaUrl } = await fetch("/api/sign-in", { - method: "POST", - headers: { "Content-Type": "application/json" }, + const { state, mfaUrl } = await fetch('/api/sign-in', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), - }).then((res) => res.json()); + }).then((res) => res.json()) - if (state === "CHALLENGE_REQUIRED") { - window.location.href = mfaUrl; + if (state === 'CHALLENGE_REQUIRED') { + window.location.href = mfaUrl } else { - router.push("/"); + router.push('/') } }} > @@ -312,38 +301,38 @@ export default function SignInPage() {
    - ); + ) } ``` Now we will use Supabase's `withPageAuth` wrapper around `getServerSideProps` to make the home page require authentication via SSR. Replace the existing code in `/pages/index.tsx` with the following: -``` -import { getUser, User, withPageAuth } from "@supabase/auth-helpers-nextjs"; -import { GetServerSideProps } from "next"; -import { useRouter } from "next/router"; -import { authsignal } from "../lib/authsignal"; +```ts +import { getUser, User, withPageAuth } from '@supabase/auth-helpers-nextjs' +import { GetServerSideProps } from 'next' +import { useRouter } from 'next/router' +import { authsignal } from '../lib/authsignal' interface Props { - user: User; - isEnrolled: boolean; + user: User + isEnrolled: boolean } export const getServerSideProps: GetServerSideProps = withPageAuth({ - redirectTo: "/sign-in", + redirectTo: '/sign-in', async getServerSideProps(ctx) { - const { user } = await getUser(ctx); + const { user } = await getUser(ctx) - const { isEnrolled } = await authsignal.getUser({userId: user.id}); + const { isEnrolled } = await authsignal.getUser({ userId: user.id }) return { props: { user, isEnrolled }, - }; + } }, -}); +}) export default function HomePage({ user, isEnrolled }: Props) { - const router = useRouter(); + const router = useRouter() return (
    @@ -351,29 +340,29 @@ export default function HomePage({ user, isEnrolled }: Props) {
    Signed in as: {user?.email}
    - +
    - ); + ) } ``` Optional: To make things look a bit nicer, you can add the following to `/styles/globals.css`: -``` +```css main { min-height: 100vh; display: flex; @@ -435,113 +424,95 @@ Now we'll replace the existing api routes in `/pages/api/` with 5 new routes: Add the following code to `/pages/api/sign-in.ts`: -``` -import { supabaseClient } from "@supabase/auth-helpers-nextjs"; -import { NextApiRequest, NextApiResponse } from "next"; -import { authsignal } from "../../lib/authsignal"; -import { setAuthCookie, setTempCookie } from "../../lib/cookies"; +```ts +import { supabaseClient } from '@supabase/auth-helpers-nextjs' +import { NextApiRequest, NextApiResponse } from 'next' +import { authsignal } from '../../lib/authsignal' +import { setAuthCookie, setTempCookie } from '../../lib/cookies' -export default async function signIn( - req: NextApiRequest, - res: NextApiResponse -) { - const { email, password } = req.body; +export default async function signIn(req: NextApiRequest, res: NextApiResponse) { + const { email, password } = req.body - const { data, error } = await supabaseClient.auth.api.signInWithEmail( - email, - password - ); + const { data, error } = await supabaseClient.auth.api.signInWithEmail(email, password) if (error || !data?.user) { - return res.send({ error }); + return res.send({ error }) } const { state, url: mfaUrl } = await authsignal.track({ - action: "signIn", + action: 'signIn', userId: data.user.id, - }); + }) - if (state === "CHALLENGE_REQUIRED") { - await setTempCookie(data, res); + if (state === 'CHALLENGE_REQUIRED') { + await setTempCookie(data, res) } else { - setAuthCookie(data, res); + setAuthCookie(data, res) } - res.send({ state, mfaUrl }); + res.send({ state, mfaUrl }) } ``` Then to handle new sign-ups add the following to `/pages/api/sign-up.ts`: -``` -import { supabaseClient } from "@supabase/auth-helpers-nextjs"; -import { Session } from "@supabase/supabase-js"; -import { NextApiRequest, NextApiResponse } from "next"; -import { setAuthCookie } from "../../lib/cookies"; +```ts +import { supabaseClient } from '@supabase/auth-helpers-nextjs' +import { Session } from '@supabase/supabase-js' +import { NextApiRequest, NextApiResponse } from 'next' +import { setAuthCookie } from '../../lib/cookies' -export default async function signUp( - req: NextApiRequest, - res: NextApiResponse -) { - const { email, password } = req.body; +export default async function signUp(req: NextApiRequest, res: NextApiResponse) { + const { email, password } = req.body - const { data, error } = await supabaseClient.auth.api.signUpWithEmail( - email, - password - ); + const { data, error } = await supabaseClient.auth.api.signUpWithEmail(email, password) if (error || !isSession(data)) { - res.send({ error }); + res.send({ error }) } else { - setAuthCookie(data, res); - res.send({ data }); + setAuthCookie(data, res) + res.send({ data }) } } -const isSession = (data: any): data is Session => !!data?.access_token; +const isSession = (data: any): data is Session => !!data?.access_token ``` To clear the auth cookies on sign-out add the following to `/pages/api/sign-out.ts`: -``` -import { supabaseClient } from "@supabase/auth-helpers-nextjs"; -import { NextApiRequest, NextApiResponse } from "next"; +```ts +import { supabaseClient } from '@supabase/auth-helpers-nextjs' +import { NextApiRequest, NextApiResponse } from 'next' -export default async function signOut( - req: NextApiRequest, - res: NextApiResponse -) { - supabaseClient.auth.api.deleteAuthCookie(req, res, { redirectTo: "/sign-in" }); +export default async function signOut(req: NextApiRequest, res: NextApiResponse) { + supabaseClient.auth.api.deleteAuthCookie(req, res, { redirectTo: '/sign-in' }) } ``` To handle the user's actions to set up MFA or manage their existing MFA settings, add the following to `/pages/api/mfa.ts`: -``` -import { getUser, withApiAuth } from "@supabase/auth-helpers-nextjs"; -import { NextApiRequest, NextApiResponse } from "next"; -import { authsignal } from "../../lib/authsignal"; +```ts +import { getUser, withApiAuth } from '@supabase/auth-helpers-nextjs' +import { NextApiRequest, NextApiResponse } from 'next' +import { authsignal } from '../../lib/authsignal' -export default withApiAuth(async function mfa( - req: NextApiRequest, - res: NextApiResponse -) { - if (req.method !== "POST") { - return res.status(405).send({ message: "Only POST requests allowed" }); +export default withApiAuth(async function mfa(req: NextApiRequest, res: NextApiResponse) { + if (req.method !== 'POST') { + return res.status(405).send({ message: 'Only POST requests allowed' }) } - const { user } = await getUser({ req, res }); + const { user } = await getUser({ req, res }) - const { isEnrolled } = req.body; + const { isEnrolled } = req.body const { url: mfaUrl } = await authsignal.track({ - action: isEnrolled ? "manageSettings" : "enroll", + action: isEnrolled ? 'manageSettings' : 'enroll', userId: user.id, redirectToSettings: isEnrolled, - }); + }) - res.send({ mfaUrl }); -}); + res.send({ mfaUrl }) +}) ``` Because the user should be authenticated with Supabase to set up or manage MFA, we can use Supabase's `withApiAuth` wrapper to protect this route. @@ -550,28 +521,25 @@ The `redirectToSettings` param specifies whether the user should be redirected t Finally we need a route to handle the redirect back from Authsignal after an MFA challenge. Add the following to `/pages/api/callback.ts`: -``` -import { NextApiRequest, NextApiResponse } from "next"; -import { authsignal } from "../../lib/authsignal"; -import { getSessionFromTempCookie, setAuthCookie } from "../../lib/cookies"; +```ts +import { NextApiRequest, NextApiResponse } from 'next' +import { authsignal } from '../../lib/authsignal' +import { getSessionFromTempCookie, setAuthCookie } from '../../lib/cookies' -export default async function callback( - req: NextApiRequest, - res: NextApiResponse -) { - const token = req.query.token as string; +export default async function callback(req: NextApiRequest, res: NextApiResponse) { + const token = req.query.token as string - const { success } = await authsignal.validateChallenge({ token }); + const { success } = await authsignal.validateChallenge({ token }) if (success) { - const session = await getSessionFromTempCookie(req); + const session = await getSessionFromTempCookie(req) if (session) { - setAuthCookie(session, res); + setAuthCookie(session, res) } } - res.redirect("/"); + res.redirect('/') } ``` From efbd726b12ab0fcb89ebe030bc9c50a9b3a87934 Mon Sep 17 00:00:00 2001 From: dannykng Date: Tue, 20 Dec 2022 14:42:08 -0800 Subject: [PATCH 077/215] Allow images from raw.githubusercontent.com --- apps/docs/next.config.mjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs index 9762c179622..bf98d6d34fd 100644 --- a/apps/docs/next.config.mjs +++ b/apps/docs/next.config.mjs @@ -48,7 +48,12 @@ const nextConfig = { basePath: '/docs', images: { dangerouslyAllowSVG: true, - domains: ['avatars.githubusercontent.com', 'github.com', 'user-images.githubusercontent.com'], + domains: [ + 'avatars.githubusercontent.com', + 'github.com', + 'user-images.githubusercontent.com', + 'raw.githubusercontent.com', + ], }, experimental: { mdxRs: true, From ec7dc6dd7e5ebe0bf60654324906a33a525f402f Mon Sep 17 00:00:00 2001 From: ethicnology Date: Wed, 21 Dec 2022 00:22:58 +0100 Subject: [PATCH 078/215] fix: deprecated docker-compose in favour of compose v2 --- apps/docs/pages/faq.mdx | 2 +- apps/docs/pages/guides/self-hosting/docker.mdx | 6 +++--- apps/www/_blog/2020-07-10-alpha-launch-postmortem.mdx | 8 ++++---- apps/www/_blog/2021-03-31-supabase-cli.mdx | 2 +- docker/docker-compose.yml | 8 ++++---- package.json | 8 ++++---- studio/README.md | 4 ++-- tests/package.json | 4 ++-- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/apps/docs/pages/faq.mdx b/apps/docs/pages/faq.mdx index eced1e89829..66c0c120969 100644 --- a/apps/docs/pages/faq.mdx +++ b/apps/docs/pages/faq.mdx @@ -16,7 +16,7 @@ Self-hosting Supabase is free. If you wish to use our cloud-platform, we provide ### How do I host Supabase? -You can use the docker-compose script [here](https://github.com/supabase/supabase/tree/master/docker), and find detailed instructions [here](/docs/guides/hosting/overview). +You can use the docker compose script [here](https://github.com/supabase/supabase/tree/master/docker), and find detailed instructions [here](/docs/guides/hosting/overview). Supabase is an amalgamation of open source tools. Some of these tools are made by Supabase (like our [Realtime Server](https://github.com/supabase/realtime)), some we support directly (like [PostgREST](http://postgrest.org/en/v7.0.0/)), and some are third-party tools (like [KonSupabase is an amalgamation open sourceg](https://github.com/Kong/kong)). diff --git a/apps/docs/pages/guides/self-hosting/docker.mdx b/apps/docs/pages/guides/self-hosting/docker.mdx index 08da99ae31c..0a32049889b 100644 --- a/apps/docs/pages/guides/self-hosting/docker.mdx +++ b/apps/docs/pages/guides/self-hosting/docker.mdx @@ -11,7 +11,7 @@ Docker is the easiest way to get started with self-hosted Supabase. You need the following installed in your system: -- [Docker](https://docs.docker.com/engine/install/) and [docker-compose](https://docs.docker.com/compose/install/) +- [Docker](https://docs.docker.com/engine/install/) and [docker compose](https://docs.docker.com/compose/install/) - [Git](https://git-scm.com/downloads) ## Quick Start @@ -31,7 +31,7 @@ cd supabase/docker cp .env.example .env # Start -docker-compose up +docker compose up ``` Now visit [http://localhost:3000](http://localhost:3000) to start using Supabase Studio. @@ -97,7 +97,7 @@ Once you have done this, you can safely comment out the `db` section of the `doc ### Setting database's `log_min_messages` -By default, `docker-compose` sets the database's `log_min_messages` configuration to `fatal` to prevent redundant logs generated by Realtime. +By default, `docker compose` sets the database's `log_min_messages` configuration to `fatal` to prevent redundant logs generated by Realtime. However, you might miss important log messages such as database errors. Configure `log_min_messages` based on your needs. ### File storage backend on macOS diff --git a/apps/www/_blog/2020-07-10-alpha-launch-postmortem.mdx b/apps/www/_blog/2020-07-10-alpha-launch-postmortem.mdx index 61bb85d236e..f0186212be6 100644 --- a/apps/www/_blog/2020-07-10-alpha-launch-postmortem.mdx +++ b/apps/www/_blog/2020-07-10-alpha-launch-postmortem.mdx @@ -37,13 +37,13 @@ GitHub stars rocketed for our two main repos, [supabase](https://github.com/supa Here are the things that survived well. -### Middleware: docker-compose up +### Middleware: docker compose up -This was the most surprising survivor. Our middleware was served from a single Ubuntu server with 4 CPUs and 8GB of RAM. This server was running our middleware using `docker-compose up`: +This was the most surprising survivor. Our middleware was served from a single Ubuntu server with 4 CPUs and 8GB of RAM. This server was running our middleware using `docker compose up`: -![This image shows in middleware architecture. We used docker-compose to pull up 5 open source tools: Kong, Realtime, PostgREST, PG-API, and PG-BOSS.](/images/blog/supabase-middleware-docker.png) +![This image shows in middleware architecture. We used docker compose to pull up 5 open source tools: Kong, Realtime, PostgREST, PG-API, and PG-BOSS.](/images/blog/supabase-middleware-docker.png) -In case you're wondering why any sane company would use that in production, it's because we weren't planning to launch - the HackerNews post was created by an early GitHub follower, while we were alpha testing, and it was too scary to migrate the middleware while it was servicing the thundering herd. All Supabase projects use the same middleware stack (sans docker-compose), so I guess this counts as as a successful load test. +In case you're wondering why any sane company would use that in production, it's because we weren't planning to launch - the HackerNews post was created by an early GitHub follower, while we were alpha testing, and it was too scary to migrate the middleware while it was servicing the thundering herd. All Supabase projects use the same middleware stack (without docker compose), so I guess this counts as a successful load test. We have since migrated our middleware to multiple ECS clusters, globally load-balanced using AWS's [Global Accelerator](https://aws.amazon.com/global-accelerator/). diff --git a/apps/www/_blog/2021-03-31-supabase-cli.mdx b/apps/www/_blog/2021-03-31-supabase-cli.mdx index b24682700de..ea8d4c94ed8 100644 --- a/apps/www/_blog/2021-03-31-supabase-cli.mdx +++ b/apps/www/_blog/2021-03-31-supabase-cli.mdx @@ -217,7 +217,7 @@ Also check `kong.yml` inside the `kong` directory where you'll see how all the s Once you're all set, you can start the stack by running: ```bash -docker-compose up +docker compose up ``` Head over to the [Self Hosting Docs](/docs/guides/self-hosting) for a more complete walk through, it also includes several [one-click deploys](/docs/guides/self-hosting#one-click-deploys), so you can easily deploy into your own cloud hosting provider. diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 70da7c553bd..659c501cde3 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,8 +1,8 @@ # Usage -# Start: docker-compose up -# With helpers: docker-compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up -# Stop: docker-compose down -# Destroy: docker-compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml down -v --remove-orphans +# Start: docker compose up +# With helpers: docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up +# Stop: docker compose down +# Destroy: docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml down -v --remove-orphans version: "3.8" diff --git a/package.json b/package.json index 4a605d28309..bf893aa1978 100644 --- a/package.json +++ b/package.json @@ -22,10 +22,10 @@ "dev:www": "turbo run dev --filter=www --parallel", "lint": "turbo run lint", "format": "prettier --write \"apps/**/*.{js,jsx,ts,tsx,css,md,json}\"", - "docker:dev": "cd docker && docker-compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up --renew-anon-volumes", - "docker:up": "cd docker && docker-compose up", - "docker:down": "cd docker && docker-compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml down --remove-orphans", - "docker:remove": "cd docker && docker-compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml rm -vfs", + "docker:dev": "cd docker && docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up --renew-anon-volumes", + "docker:up": "cd docker && docker compose up", + "docker:down": "cd docker && docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml down --remove-orphans", + "docker:remove": "cd docker && docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml rm -vfs", "test": "jest", "test:studio": "turbo run test --filter=studio", "perf:kong": "ab -t 5 -c 20 -T application/json http://localhost:8000/", diff --git a/studio/README.md b/studio/README.md index 4b9929f4a48..2c4fda0ba60 100644 --- a/studio/README.md +++ b/studio/README.md @@ -19,7 +19,7 @@ As such, the features exposed on Studio for existing deployments are limited to ## Managing Project Settings -Project settings are managed outside of the Dashboard. If you use docker-compose, you should manage the settings in your docker-compose file. If you're deploying Supabase to your own cloud, you should store your secrets and env vars in a vault or secrets manager. +Project settings are managed outside of the Dashboard. If you use docker compose, you should manage the settings in your docker-compose file. If you're deploying Supabase to your own cloud, you should store your secrets and env vars in a vault or secrets manager. ## How to contribute? @@ -51,7 +51,7 @@ Follow the [self-hosting guide](https://supabase.com/docs/guides/hosting/docker) ``` cd .. cd docker -docker-compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up +docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up ``` Once you've got that set up, update `.env` in the studio folder with the corresponding values. diff --git a/tests/package.json b/tests/package.json index 1633462bdbc..4f6b77c6c84 100644 --- a/tests/package.json +++ b/tests/package.json @@ -3,8 +3,8 @@ "version": "1.0.0", "description": "These tests can be run with Docker.", "scripts": { - "docker:up": "cd ../docker && ENABLE_EMAIL_AUTOCONFIRM=true docker-compose --env-file ./.env -f docker-compose.yml -f dev/docker-compose.dev.yml up -d", - "docker:down": "cd ../docker && docker-compose --env-file ./.env -f docker-compose.yml -f dev/docker-compose.dev.yml down", + "docker:up": "cd ../docker && ENABLE_EMAIL_AUTOCONFIRM=true docker compose --env-file ./.env -f docker-compose.yml -f dev/docker-compose.dev.yml up -d", + "docker:down": "cd ../docker && docker compose --env-file ./.env -f docker-compose.yml -f dev/docker-compose.dev.yml down", "test": "jest", "test:local": "jest --testNamePattern '^((?!\\[skip-local\\]).)*$' --testPathPattern '^((?!realtime).)*$' --detectOpenHandles", "test:stage": "jest --testNamePattern '^((?!\\[skip-stage\\]).)*$' --detectOpenHandles", From d36cc5de2601494cfc4e008bc0a674ee4071709e Mon Sep 17 00:00:00 2001 From: Long Hoang Date: Wed, 21 Dec 2022 10:13:20 +0700 Subject: [PATCH 079/215] Remove currently happening --- apps/www/pages/launch-week/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/pages/launch-week/index.tsx b/apps/www/pages/launch-week/index.tsx index 225de69fcb0..d9248069b7c 100644 --- a/apps/www/pages/launch-week/index.tsx +++ b/apps/www/pages/launch-week/index.tsx @@ -38,7 +38,7 @@ export default function launchweek() { const { isDarkMode } = useTheme() const title = 'Launch Week 6' const description = 'Supabase Launch Week 6 | 12-18 Dec 2022' - const liveDay = 'Friday' + const liveDay = null const [supabase] = useState(() => createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!) From eab07390b72e1b79f7747dea7b317ce2d309b0ea Mon Sep 17 00:00:00 2001 From: Long Hoang Date: Wed, 21 Dec 2022 10:26:35 +0700 Subject: [PATCH 080/215] Remove jump to current day on launchweek page --- apps/www/pages/launch-week/index.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/www/pages/launch-week/index.tsx b/apps/www/pages/launch-week/index.tsx index d9248069b7c..7888948b8c7 100644 --- a/apps/www/pages/launch-week/index.tsx +++ b/apps/www/pages/launch-week/index.tsx @@ -219,11 +219,6 @@ export default function launchweek() { )} >
    - -
    - Jump to current day -
    -
    {process.env.NEXT_PUBLIC_LW_STARTED && ( <> From 06d5476f2544bbfb8b1570935d532659c102db0c Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Wed, 21 Dec 2022 12:19:21 +0800 Subject: [PATCH 081/215] remove memo --- apps/docs/components/DocSearch.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/docs/components/DocSearch.tsx b/apps/docs/components/DocSearch.tsx index 19e1c85be8b..1738200c85d 100644 --- a/apps/docs/components/DocSearch.tsx +++ b/apps/docs/components/DocSearch.tsx @@ -16,7 +16,7 @@ const APP_ID = process.env.NEXT_PUBLIC_ALGOLIA_APP_ID const SearchContext = createContext(null) -function SearchProviderContent({ children }: any) { +export function SearchProvider({ children }: any) { const router = useRouter() const [isOpen, setIsOpen] = useState(false) const [initialQuery, setInitialQuery] = useState(null) @@ -115,10 +115,6 @@ function SearchProviderContent({ children }: any) { ) } -const SearchProvider = memo(SearchProviderContent) - -export { SearchProvider } - // @ts-ignore function Hit({ hit, children }) { return ( From 9bb3f4f310014e7cc2bfb020eef6d06fc68f5868 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Wed, 21 Dec 2022 12:28:07 +0800 Subject: [PATCH 082/215] remove console logs --- apps/docs/components/DocSearch.tsx | 2 +- .../NavigationMenu/NavigationMenuRefListItems.tsx | 10 ---------- apps/docs/layouts/ref/RefSubLayout.tsx | 2 -- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/apps/docs/components/DocSearch.tsx b/apps/docs/components/DocSearch.tsx index 1738200c85d..7ff1a5ece2a 100644 --- a/apps/docs/components/DocSearch.tsx +++ b/apps/docs/components/DocSearch.tsx @@ -84,7 +84,7 @@ export function SearchProvider({ children }: any) { hitComponent={Hit} transformItems={(items) => { return items.map((item, index) => { - console.log('item', item) + // console.log('item', item) // We transform the absolute URL into a relative URL to // leverage Next's preloading. const a = document.createElement('a') diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx index 2f1c8c64e02..dc309823ad7 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx @@ -170,13 +170,6 @@ interface INavigationMenuRefList { const Content: React.FC = ({ id, lib, commonSections, spec }) => { const allowedClientKeys = spec ? generateAllowedClientLibKeys(commonSections, spec) : undefined - console.log( - id && 'id changed', - lib && 'lib changed', - commonSections && 'commonSections changed', - allowedClientKeys && 'allowedClientKeys changed' - ) - let sections = commonSections const allowedKeys = allowedClientKeys @@ -226,9 +219,6 @@ const Content: React.FC = ({ id, lib, commonSections, sp }).items.map((x) => x.id) : [] - // console.log(filterIds) - // console.log(modifierIds) - return (
    diff --git a/apps/docs/layouts/ref/RefSubLayout.tsx b/apps/docs/layouts/ref/RefSubLayout.tsx index 3357e427ed1..84305bb4c00 100644 --- a/apps/docs/layouts/ref/RefSubLayout.tsx +++ b/apps/docs/layouts/ref/RefSubLayout.tsx @@ -55,7 +55,6 @@ const RefSubLayout: FC & RefSubLayoutSubComponents = (props) = } const Section: FC = (props) => { - // console.log({ props }) return (
    = (props) => { } const EducationSection: FC = ({ icon, hideTitle = false, ...props }) => { - // console.log({ props }) return (
    Date: Wed, 21 Dec 2022 13:27:15 +0800 Subject: [PATCH 083/215] mobile menu updates --- apps/docs/layouts/SiteLayout.tsx | 582 +++++++++++++++++-------------- apps/docs/pages/_app.tsx | 1 - 2 files changed, 315 insertions(+), 268 deletions(-) diff --git a/apps/docs/layouts/SiteLayout.tsx b/apps/docs/layouts/SiteLayout.tsx index 45f734e61ca..8d8a116c7ad 100644 --- a/apps/docs/layouts/SiteLayout.tsx +++ b/apps/docs/layouts/SiteLayout.tsx @@ -7,16 +7,310 @@ import TopNavBarRef from '~/components/Navigation/NavigationMenu/TopNavBarRef' import FooterHelpCallout from '~/components/FooterHelpCallout' -import { - menuState, - // useMenuLevelId, - // useMenuMobileOpen -} from '~/hooks/useMenuState' -import { useEffect } from 'react' +import { menuState, useMenuLevelId, useMenuMobileOpen } from '~/hooks/useMenuState' +import { memo, useEffect } from 'react' import Footer from '~/components/Navigation/Footer' -const SiteLayout = ({ children }) => { +const levelsData = { + home: { + icon: '/docs/img/icons/menu/home', + name: 'Home', + }, + gettingstarted: { + icon: '/docs/img/icons/menu/getting-started', + name: 'Getting Started', + }, + database: { + icon: '/docs/img/icons/menu/database', + name: 'Database', + }, + auth: { + icon: '/docs/img/icons/menu/auth', + name: 'Auth', + }, + functions: { + icon: '/docs/img/icons/menu/functions', + name: 'Functions', + }, + realtime: { + icon: '/docs/img/icons/menu/realtime', + name: 'Realtime', + }, + storage: { + icon: '/docs/img/icons/menu/storage', + name: 'Storage', + }, + platform: { + icon: '/docs/img/icons/menu/platform', + name: 'Platform', + }, + resources: { + icon: '/docs/img/icons/menu/resources', + name: 'Resources', + }, + self_hosting: { + icon: '/docs/img/icons/menu/self-hosting', + name: 'Self-Hosting', + }, + integrations: { + icon: '/docs/img/icons/menu/integrations', + name: 'Integrations', + }, + reference_javascript_v1: { + icon: '/docs/img/icons/menu/reference-javascript', + name: 'Javascript Reference v1.0', + }, + reference_javascript_v2: { + icon: '/docs/img/icons/menu/reference-javascript', + name: 'Javascript Reference v2.0', + }, + reference_dart_v0: { + icon: '/docs/img/icons/menu/reference-dart', + name: 'Dart Reference v0.0', + }, + reference_dart_v1: { + icon: '/docs/img/icons/menu/reference-dart', + name: 'Dart Reference v0.0', + }, + reference_cli: { + icon: '/docs/img/icons/menu/reference-cli', + name: 'CLI Reference', + }, + reference_api: { + icon: '/docs/img/icons/menu/reference-api', + name: 'Management API Reference', + }, + reference_self_hosting_auth: { + icon: '/docs/img/icons/menu/reference-auth', + name: 'Auth Server Reference', + }, + reference_self_hosting_storage: { + icon: '/docs/img/icons/menu/reference-storage', + name: 'Storage Server Reference', + }, + reference_self_hosting_realtime: { + icon: '/docs/img/icons/menu/reference-realtime', + name: 'Realtime Server Reference', + }, +} + +const MobileHeader = memo(function MobileHeader() { + const mobileMenuOpen = useMenuMobileOpen() + const menuLevel = useMenuLevelId() + + return ( +
    + +
    + +
    + + {mobileMenuOpen + ? 'Close' + : menuLevel + ? levelsData[menuLevel].name + : levelsData['home'].name} + +
    + ) +}) + +const MobileMenuBackdrop = memo(function MobileMenuBackdrop() { + const mobileMenuOpen = useMenuMobileOpen() + return ( +
    menuState.setMenuMobileOpen(!mobileMenuOpen)} + >
    + ) +}) + +// const SideMenu = memo(function SideMenu() { +// const mobileMenuOpen = useMenuMobileOpen() + +// return ( +//
    +// +//
    +// ) +// }) + +const SideMenu = memo(function SideMenu() { + const mobileMenuOpen = useMenuMobileOpen() + + return ( +
    + +
    + ) +}) + +const HeaderLogo = memo(function HeaderLogo() { const { isDarkMode } = useTheme() + return ( + + + Supabase Logo + DOCS + + + ) +}) + +const Container = memo(function Container(props) { + const mobileMenuOpen = useMenuMobileOpen() + + return ( +
    +
    {props.children}
    +
    + ) +}) + +const NavContainer = memo(function NavContainer() { + const mobileMenuOpen = useMenuMobileOpen() + + return ( +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    + ) +}) + +const SiteLayout = ({ children }) => { + // const mobileMenuOpen = useMenuMobileOpen() useEffect(() => { const key = localStorage.getItem('supabaseDarkMode') @@ -28,278 +322,32 @@ const SiteLayout = ({ children }) => { } }, []) - const menuLevel = 'home' - // useMenuLevelId() - const mobileMenuOpen = false - // useMenuMobileOpen() - - const levelsData = { - home: { - icon: '/docs/img/icons/menu/home', - name: 'Home', - }, - gettingstarted: { - icon: '/docs/img/icons/menu/getting-started', - name: 'Getting Started', - }, - database: { - icon: '/docs/img/icons/menu/database', - name: 'Database', - }, - auth: { - icon: '/docs/img/icons/menu/auth', - name: 'Auth', - }, - functions: { - icon: '/docs/img/icons/menu/functions', - name: 'Functions', - }, - realtime: { - icon: '/docs/img/icons/menu/realtime', - name: 'Realtime', - }, - storage: { - icon: '/docs/img/icons/menu/storage', - name: 'Storage', - }, - platform: { - icon: '/docs/img/icons/menu/platform', - name: 'Platform', - }, - resources: { - icon: '/docs/img/icons/menu/resources', - name: 'Resources', - }, - self_hosting: { - icon: '/docs/img/icons/menu/self-hosting', - name: 'Self-Hosting', - }, - integrations: { - icon: '/docs/img/icons/menu/integrations', - name: 'Integrations', - }, - reference_javascript_v1: { - icon: '/docs/img/icons/menu/reference-javascript', - name: 'Javascript Reference v1.0', - }, - reference_javascript_v2: { - icon: '/docs/img/icons/menu/reference-javascript', - name: 'Javascript Reference v2.0', - }, - reference_dart_v0: { - icon: '/docs/img/icons/menu/reference-dart', - name: 'Dart Reference v0.0', - }, - reference_dart_v1: { - icon: '/docs/img/icons/menu/reference-dart', - name: 'Dart Reference v0.0', - }, - reference_cli: { - icon: '/docs/img/icons/menu/reference-cli', - name: 'CLI Reference', - }, - reference_api: { - icon: '/docs/img/icons/menu/reference-api', - name: 'Management API Reference', - }, - reference_self_hosting_auth: { - icon: '/docs/img/icons/menu/reference-auth', - name: 'Auth Server Reference', - }, - reference_self_hosting_storage: { - icon: '/docs/img/icons/menu/reference-storage', - name: 'Storage Server Reference', - }, - reference_self_hosting_realtime: { - icon: '/docs/img/icons/menu/reference-realtime', - name: 'Realtime Server Reference', - }, - } - return (
    - {/* */} -
    -
    ) diff --git a/apps/docs/pages/_app.tsx b/apps/docs/pages/_app.tsx index 52a0b995643..0736cff9c2b 100644 --- a/apps/docs/pages/_app.tsx +++ b/apps/docs/pages/_app.tsx @@ -13,7 +13,6 @@ import '../styles/main.scss?v=1.0.0' import '../styles/new-docs.scss' import '../styles/prism-okaidia.scss' import { post } from '~/lib/fetchWrappers' -import { menuState } from '~/hooks/useMenuState' function MyApp({ Component, pageProps }: AppPropsWithLayout) { const router = useRouter() From 13c070e81d17735731f30cfbbeabc80233821b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Thu, 15 Dec 2022 14:36:17 +0100 Subject: [PATCH 084/215] feat(hCaptcha): captcha for subscription/payment method changes --- .../AddNewPaymentMethodModal.tsx | 49 ++++++++++++++++--- .../interfaces/Billing/Billing.utils.ts | 4 +- .../interfaces/Billing/EnterpriseUpdate.tsx | 31 ++++++++++-- .../Billing/ExitSurvey/ExitSurvey.tsx | 30 ++++++++++-- .../PaymentSummaryPanel.tsx | 9 +++- .../interfaces/Billing/ProUpgrade.tsx | 32 ++++++++++-- 6 files changed, 135 insertions(+), 20 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 8e8e7e865b6..7808f106114 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useState } from 'react' +import { FC, useEffect, useRef, useState } from 'react' import { IconLoader, Modal } from 'ui' import { loadStripe } from '@stripe/stripe-js' import { Elements } from '@stripe/react-stripe-js' @@ -7,6 +7,7 @@ import { useStore } from 'hooks' import { post } from 'lib/common/fetch' import { API_URL, STRIPE_PUBLIC_KEY } from 'lib/constants' import AddPaymentMethodForm from './AddPaymentMethodForm' +import HCaptcha from '@hcaptcha/react-hcaptcha' interface Props { visible: boolean @@ -20,18 +21,38 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = const { ui } = useStore() const [intent, setIntent] = useState() - useEffect(() => { - if (visible) { - setupIntent() - } - }, [visible]) + const [captchaLoaded, setCaptchaLoaded] = useState(false) - const setupIntent = async () => { + const [captchaToken, setCaptchaToken] = useState(null) + const captchaRef = useRef(null) + + useEffect(() => { + if (visible && captchaLoaded) { + let token = captchaToken + if (!token) { + captchaRef.current?.execute({ async: true }).then((captchaResponse) => { + return setupIntent(captchaResponse?.response ?? undefined) + }) + } + } + }, [visible, captchaLoaded]) + + const onCaptchaLoaded = () => { + setCaptchaLoaded(true) + } + + const setupIntent = async (hcaptchaToken: string | undefined) => { setIntent(undefined) + const orgSlug = ui.selectedOrganization?.slug ?? '' - const intent = await post(`${API_URL}/organizations/${orgSlug}/payments/setup-intent`, {}) + const intent = await post(`${API_URL}/organizations/${orgSlug}/payments/setup-intent`, { + hcaptchaToken, + }) if (intent.error) { + setCaptchaToken(null) + captchaRef.current?.resetCaptcha() + return ui.setNotification({ category: 'error', message: intent.error.message, @@ -56,6 +77,18 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = onCancel={onCancel} className="PAYMENT" > + { + setCaptchaToken(token) + }} + onExpire={() => { + setCaptchaToken(null) + }} + />
    {intent !== undefined ? ( diff --git a/studio/components/interfaces/Billing/Billing.utils.ts b/studio/components/interfaces/Billing/Billing.utils.ts index 84e1e448694..67f98f93e7b 100644 --- a/studio/components/interfaces/Billing/Billing.utils.ts +++ b/studio/components/interfaces/Billing/Billing.utils.ts @@ -52,7 +52,8 @@ export const formSubscriptionUpdatePayload = ( }, nonChangeableAddons: SubscriptionAddon[], selectedPaymentMethod: string, - region: string + region: string, + hcaptchaToken: string | undefined ) => { const { computeSize, pitrDuration, customDomains } = selectedAddons @@ -78,6 +79,7 @@ export const formSubscriptionUpdatePayload = ( addons, proration_date, payment_method: selectedPaymentMethod, + hcaptchaToken } } diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index bf98cd7e4aa..430b8dfcc68 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -1,4 +1,4 @@ -import { FC, useState, useEffect } from 'react' +import { FC, useState, useEffect, useRef } from 'react' import { useRouter } from 'next/router' import { Transition } from '@headlessui/react' @@ -6,6 +6,7 @@ import { useStore, useFlag } from 'hooks' import { getURL } from 'lib/helpers' import { post, patch } from 'lib/common/fetch' import { API_URL, PROJECT_STATUS } from 'lib/constants' +import HCaptcha from '@hcaptcha/react-hcaptcha' import Divider from 'components/ui/Divider' import { SubscriptionAddon } from './AddOns/AddOns.types' @@ -45,6 +46,9 @@ const EnterpriseUpdate: FC = ({ const isCustomDomainsEnabled = useFlag('customDomains') const isPITRSelfServeEnabled = useFlag('pitrSelfServe') + const [captchaToken, setCaptchaToken] = useState(null) + const captchaRef = useRef(null) + const projectId = ui.selectedProject?.id ?? -1 const projectRef = ui.selectedProject?.ref ?? 'default' const projectRegion = ui.selectedProject?.region ?? '' @@ -110,7 +114,8 @@ const EnterpriseUpdate: FC = ({ selectedAddons, nonChangeableAddons, selectedPaymentMethodId, - projectRegion + projectRegion, + undefined ), tier: currentSubscription.tier.price_id, } @@ -129,6 +134,12 @@ const EnterpriseUpdate: FC = ({ // Last todo to support enterprise billing on dashboard + E2E test const onConfirmPayment = async () => { + let token = captchaToken + if (!token) { + const captchaResponse = await captchaRef.current?.execute({ async: true }) + token = captchaResponse?.response ?? null + } + const payload = { ...formSubscriptionUpdatePayload( currentSubscription, @@ -136,7 +147,8 @@ const EnterpriseUpdate: FC = ({ selectedAddons, nonChangeableAddons, selectedPaymentMethodId, - projectRegion + projectRegion, + token ?? undefined ), tier: currentSubscription.tier.price_id, } @@ -273,6 +285,19 @@ const EnterpriseUpdate: FC = ({ setShowAddPaymentMethodModal(true) }} onConfirmPayment={onConfirmPayment} + captcha={ + { + setCaptchaToken(token) + }} + onExpire={() => { + setCaptchaToken(null) + }} + /> + } />
    diff --git a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx index c1838f7c233..7d7a02e26b1 100644 --- a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx +++ b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx @@ -11,6 +11,7 @@ import { CANCELLATION_REASONS } from '../Billing.constants' import { UpdateSuccess } from '../' import { SubscriptionPreview } from '../Billing.types' import { StripeSubscription } from 'components/interfaces/Billing' +import HCaptcha from '@hcaptcha/react-hcaptcha' interface Props { freeTier: any @@ -24,6 +25,9 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { const projectId = ui.selectedProject?.id ?? -1 const projectRef = ui.selectedProject?.ref + const [captchaToken, setCaptchaToken] = useState(null) + const captchaRef = useRef(null) + const formRef = useRef() const initialValues = { message: '' } @@ -99,6 +103,12 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { const downgradeProject = async (values?: any) => { const downgradeMessage = values?.message ?? message + let token = captchaToken + if (!token) { + const captchaResponse = await captchaRef.current?.execute({ async: true }) + token = captchaResponse?.response ?? null + } + try { setIsSubmitting(true) @@ -110,6 +120,7 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { tier, addons, proration_date, + hcaptchaToken: token ?? undefined }) if (res?.error) { @@ -139,9 +150,9 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { } const feedbackRes = await post(`${API_URL}/feedback/downgrade`, { projectRef, - reasons: selectedReasons.reduce((a, b) => `${a}- ${b}\n`, ''), - additionalFeedback: downgradeMessage, - exitAction: 'downgrade', + reasons: selectedReasons.reduce((a, b) => `${a}- ${b}\n`, ''), + additionalFeedback: downgradeMessage, + exitAction: 'downgrade', }) if (feedbackRes.error) throw feedbackRes.error } catch (error: any) { @@ -245,6 +256,19 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { credits

    +
    + { + setCaptchaToken(token) + }} + onExpire={() => { + setCaptchaToken(null) + }} + /> +
    )} diff --git a/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx b/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx index 078d2a067d2..9e0db842011 100644 --- a/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx +++ b/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx @@ -1,4 +1,4 @@ -import { FC, useState } from 'react' +import { FC, useRef, useState } from 'react' import * as Tooltip from '@radix-ui/react-tooltip' import { Listbox, IconLoader, Button, IconPlus, IconAlertCircle, IconCreditCard } from 'ui' import { PermissionAction } from '@supabase/shared-types/out/constants' @@ -44,6 +44,8 @@ interface Props { onSelectAddNewPaymentMethod: () => void onConfirmPayment: () => void isSubmitting: boolean + + captcha: React.ReactNode } // Use case of this panel is actually only for upgrading from Free to Pro @@ -72,6 +74,7 @@ const PaymentSummaryPanel: FC = ({ onSelectAddNewPaymentMethod, onConfirmPayment, isSubmitting, + captcha, }) => { const { ui } = useStore() const projectRegion = ui.selectedProject?.region @@ -120,7 +123,7 @@ const PaymentSummaryPanel: FC = ({ ).toFixed(2) } - const validateOrder = () => { + const validateOrder = async () => { const error = validateSubscriptionUpdatePayload(selectedAddons) if (error) { return ui.setNotification({ @@ -382,6 +385,8 @@ const PaymentSummaryPanel: FC = ({ )} +
    {captcha}
    +
    diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index 7fe2e78c578..68ff40f839e 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useState } from 'react' +import { FC, useEffect, useRef, useState } from 'react' import { Transition } from '@headlessui/react' import { useRouter } from 'next/router' import * as Tooltip from '@radix-ui/react-tooltip' @@ -29,6 +29,7 @@ import { } from './AddOns/AddOns.utils' import BackButton from 'components/ui/BackButton' import SupportPlan from './AddOns/SupportPlan' +import HCaptcha from '@hcaptcha/react-hcaptcha' // Do not allow compute size changes for af-south-1 @@ -52,6 +53,9 @@ const ProUpgrade: FC = ({ const isCustomDomainsEnabled = useFlag('customDomains') const isPITRSelfServeEnabled = useFlag('pitrSelfServe') + const [captchaToken, setCaptchaToken] = useState(null) + const captchaRef = useRef(null) + const { addons } = products const computeSizes = formatComputeSizes(addons) const pitrDurationOptions = formatPITROptions(addons) @@ -133,7 +137,8 @@ const ProUpgrade: FC = ({ selectedAddons, nonChangeableAddons, selectedPaymentMethodId, - projectRegion + projectRegion, + undefined ) setIsRefreshingPreview(true) @@ -149,13 +154,19 @@ const ProUpgrade: FC = ({ } const onConfirmPayment = async () => { + let token = captchaToken + if (!token) { + const captchaResponse = await captchaRef.current?.execute({ async: true }) + token = captchaResponse?.response ?? null + } const payload = formSubscriptionUpdatePayload( currentSubscription, selectedTier, selectedAddons, nonChangeableAddons, selectedPaymentMethodId, - projectRegion + projectRegion, + token ?? undefined, ) setIsSubmitting(true) @@ -165,6 +176,8 @@ const ProUpgrade: FC = ({ category: 'error', message: `Failed to update subscription: ${res?.error?.message}`, }) + setCaptchaToken(null) + captchaRef.current?.resetCaptcha() } else { if (isChangingComputeSize) { app.onProjectStatusUpdated(projectId, PROJECT_STATUS.RESTORING) @@ -320,6 +333,19 @@ const ProUpgrade: FC = ({ }} onConfirmPayment={onConfirmPayment} isSubmitting={isSubmitting} + captcha={ + { + setCaptchaToken(token) + }} + onExpire={() => { + setCaptchaToken(null) + }} + /> + } />
    From faf962fd92d42f12301e86be1571271ef0de00cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Thu, 15 Dec 2022 18:28:33 +0100 Subject: [PATCH 085/215] slightly improve handling --- .../AddNewPaymentMethodModal.tsx | 17 +++++++++++------ .../interfaces/Billing/EnterpriseUpdate.tsx | 2 ++ .../Billing/ExitSurvey/ExitSurvey.tsx | 4 +++- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 7808f106114..cb355ba8f29 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -27,14 +27,19 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = const captchaRef = useRef(null) useEffect(() => { - if (visible && captchaLoaded) { - let token = captchaToken - if (!token) { - captchaRef.current?.execute({ async: true }).then((captchaResponse) => { - return setupIntent(captchaResponse?.response ?? undefined) - }) + const loadPaymentForm = async () => { + if (visible && captchaLoaded) { + let token = captchaToken + if (!token) { + const captchaResponse = await captchaRef.current?.execute({ async: true }) + token = captchaResponse?.response ?? null + } + + await setupIntent(token ?? undefined) } } + + loadPaymentForm() }, [visible, captchaLoaded]) const onCaptchaLoaded = () => { diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index 430b8dfcc68..8ea5ff543da 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -156,6 +156,8 @@ const EnterpriseUpdate: FC = ({ setIsSubmitting(true) const res = await patch(`${API_URL}/projects/${projectRef}/subscription`, payload) if (res?.error) { + setCaptchaToken(null) + captchaRef.current?.resetCaptcha() ui.setNotification({ category: 'error', message: `Failed to update subscription: ${res?.error?.message}`, diff --git a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx index 7d7a02e26b1..7144a13c948 100644 --- a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx +++ b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx @@ -120,10 +120,12 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { tier, addons, proration_date, - hcaptchaToken: token ?? undefined + hcaptchaToken: token ?? undefined, }) if (res?.error) { + setCaptchaToken(null) + captchaRef.current?.resetCaptcha() return ui.setNotification({ category: 'error', message: `Failed to cancel subscription: ${res?.error?.message}`, From ba5fa484484427fea6c6ddb0225c2e46bbc7b728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 08:50:41 +0100 Subject: [PATCH 086/215] reset captcha --- .../AddNewPaymentMethodModal.tsx | 12 ++++++++---- .../interfaces/Billing/EnterpriseUpdate.tsx | 9 +++++++-- .../interfaces/Billing/ExitSurvey/ExitSurvey.tsx | 9 +++++++-- studio/components/interfaces/Billing/ProUpgrade.tsx | 12 +++++++++--- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index cb355ba8f29..edda104deb6 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -46,6 +46,11 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = setCaptchaLoaded(true) } + const resetCaptcha = () => { + setCaptchaToken(null) + captchaRef.current?.resetCaptcha() + } + const setupIntent = async (hcaptchaToken: string | undefined) => { setIntent(undefined) @@ -54,10 +59,9 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = hcaptchaToken, }) - if (intent.error) { - setCaptchaToken(null) - captchaRef.current?.resetCaptcha() + resetCaptcha() + if (intent.error) { return ui.setNotification({ category: 'error', message: intent.error.message, @@ -65,7 +69,7 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = }) } - setIntent(intent) + captchaRef.current?.resetCaptcha() } const options = { diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index 8ea5ff543da..77b10ab0809 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -132,6 +132,11 @@ const EnterpriseUpdate: FC = ({ setIsRefreshingPreview(false) } + const resetCaptcha = () => { + setCaptchaToken(null) + captchaRef.current?.resetCaptcha() + } + // Last todo to support enterprise billing on dashboard + E2E test const onConfirmPayment = async () => { let token = captchaToken @@ -155,9 +160,9 @@ const EnterpriseUpdate: FC = ({ setIsSubmitting(true) const res = await patch(`${API_URL}/projects/${projectRef}/subscription`, payload) + resetCaptcha() + if (res?.error) { - setCaptchaToken(null) - captchaRef.current?.resetCaptcha() ui.setNotification({ category: 'error', message: `Failed to update subscription: ${res?.error?.message}`, diff --git a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx index 7144a13c948..52ad8ea889e 100644 --- a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx +++ b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx @@ -100,6 +100,11 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { } } + const resetCaptcha = () => { + setCaptchaToken(null) + captchaRef.current?.resetCaptcha() + } + const downgradeProject = async (values?: any) => { const downgradeMessage = values?.message ?? message @@ -123,9 +128,9 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { hcaptchaToken: token ?? undefined, }) + resetCaptcha() + if (res?.error) { - setCaptchaToken(null) - captchaRef.current?.resetCaptcha() return ui.setNotification({ category: 'error', message: `Failed to cancel subscription: ${res?.error?.message}`, diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index 68ff40f839e..f299c2308ce 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -153,6 +153,11 @@ const ProUpgrade: FC = ({ setIsRefreshingPreview(false) } + const resetCaptcha = () => { + setCaptchaToken(null) + captchaRef.current?.resetCaptcha() + } + const onConfirmPayment = async () => { let token = captchaToken if (!token) { @@ -166,18 +171,19 @@ const ProUpgrade: FC = ({ nonChangeableAddons, selectedPaymentMethodId, projectRegion, - token ?? undefined, + token ?? undefined ) setIsSubmitting(true) const res = await patch(`${API_URL}/projects/${projectRef}/subscription`, payload) + + resetCaptcha() + if (res?.error) { ui.setNotification({ category: 'error', message: `Failed to update subscription: ${res?.error?.message}`, }) - setCaptchaToken(null) - captchaRef.current?.resetCaptcha() } else { if (isChangingComputeSize) { app.onProjectStatusUpdated(projectId, PROJECT_STATUS.RESTORING) From 00b94fdc1f9bfea478f3ec808902525451309d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 10:03:44 +0100 Subject: [PATCH 087/215] payment method modal --- .../AddNewPaymentMethodModal.tsx | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index edda104deb6..9da33e2f215 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -36,6 +36,7 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = } await setupIntent(token ?? undefined) + resetCaptcha() } } @@ -59,17 +60,15 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = hcaptchaToken, }) - resetCaptcha() - if (intent.error) { return ui.setNotification({ category: 'error', message: intent.error.message, error: intent.error, }) + } else { + setIntent(intent) } - - captchaRef.current?.resetCaptcha() } const options = { @@ -77,15 +76,9 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = appearance: { theme: 'night', labels: 'floating' }, } as any + // Needs to be re-rendered properly for hCaptcha to work seamlessly return ( - + <> = ({ visible, returnUrl, onCancel }) = setCaptchaToken(null) }} /> -
    - {intent !== undefined ? ( - - - - ) : ( -
    - -
    - )} -
    -
    + +
    + {intent !== undefined ? ( + + + + ) : ( +
    + +
    + )} +
    +
    + ) } From 5d213e9b53bbcd6205e3136aee5e730e084012f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 10:21:14 +0100 Subject: [PATCH 088/215] log captcha error for debuggability --- .../AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 9da33e2f215..5b23757d3c0 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -76,7 +76,6 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = appearance: { theme: 'night', labels: 'floating' }, } as any - // Needs to be re-rendered properly for hCaptcha to work seamlessly return ( <> = ({ visible, returnUrl, onCancel }) = sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY!} size="invisible" onLoad={onCaptchaLoaded} + onError={(err) => console.error(err)} onVerify={(token) => { setCaptchaToken(token) }} From ceb5e9b4f80309aa805e392b384f20f63ce9e52d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 13:00:30 +0100 Subject: [PATCH 089/215] try to fix hcaptcha in modal --- .../AddNewPaymentMethodModal.tsx | 65 ++++++++++--------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 5b23757d3c0..e28d65ec9fd 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useRef, useState } from 'react' +import { FC, useCallback, useEffect, useRef, useState } from 'react' import { IconLoader, Modal } from 'ui' import { loadStripe } from '@stripe/stripe-js' import { Elements } from '@stripe/react-stripe-js' @@ -24,32 +24,41 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = const [captchaLoaded, setCaptchaLoaded] = useState(false) const [captchaToken, setCaptchaToken] = useState(null) - const captchaRef = useRef(null) + const [captchaRef, setCaptchaRef] = useState(null) + + const captchaRefCallback = useCallback((node) => { + setCaptchaRef(node) + }, []) useEffect(() => { const loadPaymentForm = async () => { - if (visible && captchaLoaded) { + if (visible && captchaLoaded && captchaRef) { + console.log('load payment form', { visible, captchaLoaded }) let token = captchaToken if (!token) { - const captchaResponse = await captchaRef.current?.execute({ async: true }) + const captchaResponse = await captchaRef.execute({ async: true }) token = captchaResponse?.response ?? null } + console.log('Setting up intent', { token }) + await setupIntent(token ?? undefined) resetCaptcha() } } loadPaymentForm() - }, [visible, captchaLoaded]) + }, [visible, captchaLoaded, captchaRef]) const onCaptchaLoaded = () => { + console.log('on captcha loaded') setCaptchaLoaded(true) } const resetCaptcha = () => { + console.log('Resetting captcha') setCaptchaToken(null) - captchaRef.current?.resetCaptcha() + captchaRef?.resetCaptcha() } const setupIntent = async (hcaptchaToken: string | undefined) => { @@ -77,9 +86,16 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = } as any return ( - <> + = ({ visible, returnUrl, onCancel }) = setCaptchaToken(null) }} /> - -
    - {intent !== undefined ? ( - - - - ) : ( -
    - -
    - )} -
    -
    - +
    + {intent !== undefined ? ( + + + + ) : ( +
    + +
    + )} +
    +
    ) } From 5939182bc938026e732fa1cb9a54de7c1a4c106b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 13:14:59 +0100 Subject: [PATCH 090/215] help me --- .../AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index e28d65ec9fd..ff7700e1f0e 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -27,13 +27,14 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = const [captchaRef, setCaptchaRef] = useState(null) const captchaRefCallback = useCallback((node) => { + console.log({ node }) setCaptchaRef(node) }, []) useEffect(() => { const loadPaymentForm = async () => { + console.log('load payment form', { visible, captchaLoaded, captchaRef }) if (visible && captchaLoaded && captchaRef) { - console.log('load payment form', { visible, captchaLoaded }) let token = captchaToken if (!token) { const captchaResponse = await captchaRef.execute({ async: true }) From 861cf7899b6aa62a75707468f6fd913758822669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 13:37:15 +0100 Subject: [PATCH 091/215] render only on visible --- .../AddNewPaymentMethodModal.tsx | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index ff7700e1f0e..8fdcc6ef764 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -34,7 +34,7 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = useEffect(() => { const loadPaymentForm = async () => { console.log('load payment form', { visible, captchaLoaded, captchaRef }) - if (visible && captchaLoaded && captchaRef) { + if (visible && captchaRef && captchaLoaded) { let token = captchaToken if (!token) { const captchaResponse = await captchaRef.execute({ async: true }) @@ -49,7 +49,7 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = } loadPaymentForm() - }, [visible, captchaLoaded, captchaRef]) + }, [visible, captchaRef, captchaLoaded]) const onCaptchaLoaded = () => { console.log('on captcha loaded') @@ -95,19 +95,21 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = onCancel={onCancel} className="PAYMENT" > - console.error(err)} - onVerify={(token) => { - setCaptchaToken(token) - }} - onExpire={() => { - setCaptchaToken(null) - }} - /> + {visible && ( + console.error(err)} + onLoad={() => onCaptchaLoaded()} + onVerify={(token) => { + setCaptchaToken(token) + }} + onExpire={() => { + setCaptchaToken(null) + }} + /> + )}
    {intent !== undefined ? ( From 427fb8c56edc39647848d26e4b9a9dd0bc1e47b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 13:57:28 +0100 Subject: [PATCH 092/215] jesus take the wheel --- .../AddNewPaymentMethodModal.tsx | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 8fdcc6ef764..d40e165c652 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -27,10 +27,14 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = const [captchaRef, setCaptchaRef] = useState(null) const captchaRefCallback = useCallback((node) => { - console.log({ node }) setCaptchaRef(node) }, []) + useEffect(() => { + setCaptchaLoaded(true) + // @ts-ignore + }, [window.hcaptcha]) + useEffect(() => { const loadPaymentForm = async () => { console.log('load payment form', { visible, captchaLoaded, captchaRef }) @@ -51,11 +55,6 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = loadPaymentForm() }, [visible, captchaRef, captchaLoaded]) - const onCaptchaLoaded = () => { - console.log('on captcha loaded') - setCaptchaLoaded(true) - } - const resetCaptcha = () => { console.log('Resetting captcha') setCaptchaToken(null) @@ -86,30 +85,31 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = appearance: { theme: 'night', labels: 'floating' }, } as any + const onLocalCancel = () => { + setIntent(undefined) + return onCancel() + } + return ( - {visible && ( - console.error(err)} - onLoad={() => onCaptchaLoaded()} - onVerify={(token) => { - setCaptchaToken(token) - }} - onExpire={() => { - setCaptchaToken(null) - }} - /> - )} + { + setCaptchaToken(token) + }} + onExpire={() => { + setCaptchaToken(null) + }} + />
    {intent !== undefined ? ( From 52548ec10416ab22e5f8c3619427655bd0f79db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 14:10:04 +0100 Subject: [PATCH 093/215] pain --- .../AddNewPaymentMethodModal.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index d40e165c652..0883d871175 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -30,6 +30,13 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = setCaptchaRef(node) }, []) + /** + * We want to load the Stripe payment elements only after successfully verifying captcha. + * To execute hCaptcha when displaying the modal, we need to ensure hCaptcha is actually loaded. + * The `onLoad` method from hCaptcha is only executed ONCE for the entire application, so we cannot listen to it. + * One way to find out if hCaptcha has been initialized, is checking `window.hcaptcha`. When checking via window, + * we know if the hCaptcha has been initialized from a different part of the app or through this component. + */ useEffect(() => { setCaptchaLoaded(true) // @ts-ignore @@ -37,7 +44,6 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = useEffect(() => { const loadPaymentForm = async () => { - console.log('load payment form', { visible, captchaLoaded, captchaRef }) if (visible && captchaRef && captchaLoaded) { let token = captchaToken if (!token) { @@ -45,8 +51,6 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = token = captchaResponse?.response ?? null } - console.log('Setting up intent', { token }) - await setupIntent(token ?? undefined) resetCaptcha() } @@ -56,7 +60,6 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = }, [visible, captchaRef, captchaLoaded]) const resetCaptcha = () => { - console.log('Resetting captcha') setCaptchaToken(null) captchaRef?.resetCaptcha() } @@ -113,7 +116,7 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) =
    {intent !== undefined ? ( - + ) : (
    From 284ce90b1767524dbada3d2fb49f7d0ace7010fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 14:32:16 +0100 Subject: [PATCH 094/215] move captcha outside of modal --- .../AddNewPaymentMethodModal.tsx | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 0883d871175..065027c7847 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -94,14 +94,9 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = } return ( - + // We cant display the hCaptcha in the modal, as the modal auto-closes when clicking the captcha + // So we only show the modal if the captcha has been executed successfully (intent loaded) + <> = ({ visible, returnUrl, onCancel }) = setCaptchaToken(null) }} /> -
    - {intent !== undefined ? ( + + +
    - ) : ( -
    - -
    - )} -
    -
    +
    +
    + ) } From cd8d8713e67b0cf09e7fb768757b1223774d87c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 14:42:54 +0100 Subject: [PATCH 095/215] log open/close --- .../AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 065027c7847..59a11d8c36d 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -104,6 +104,8 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = onVerify={(token) => { setCaptchaToken(token) }} + onClose={() => console.log('onclose')} + onOpen={() => console.log('onopen')} onExpire={() => { setCaptchaToken(null) }} From 5fc41f27a17a03561e0b78bbbcbdae1834153f5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 14:50:17 +0100 Subject: [PATCH 096/215] is this it --- .../AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 59a11d8c36d..5f18c9dff3b 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -104,8 +104,7 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = onVerify={(token) => { setCaptchaToken(token) }} - onClose={() => console.log('onclose')} - onOpen={() => console.log('onopen')} + onClose={onLocalCancel} onExpire={() => { setCaptchaToken(null) }} From 47216df6d0401bc6d47997f20f0aca2f770c2a62 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 20 Dec 2022 14:26:42 +0000 Subject: [PATCH 097/215] adds hcaptcha loaded store --- package-lock.json | 61 +++++++++++++++++++ .../AddNewPaymentMethodModal.tsx | 23 ++----- studio/package.json | 1 + studio/pages/_app.tsx | 2 + studio/stores/hcaptcha-loaded-store.tsx | 31 ++++++++++ 5 files changed, 101 insertions(+), 17 deletions(-) create mode 100644 studio/stores/hcaptcha-loaded-store.tsx diff --git a/package-lock.json b/package-lock.json index 5f6dcf98766..938bb21e236 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37607,6 +37607,7 @@ "uniforms-bootstrap4": "^3.6.2", "uniforms-bridge-json-schema": "^3.6.2", "uuid": "^8.3.2", + "valtio": "^1.7.6", "yup": "^0.32.11", "yup-password": "^0.2.2", "zxcvbn": "^4.4.2" @@ -37829,6 +37830,11 @@ "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-9.3.1.tgz", "integrity": "sha512-/Yvsd2D7miYB4HLJ3hOOS0+vnowQpaT75FsHzr/y5M9P4q9bwa7RcbW2YdH6KZBn8ceLbKGnHxMZ1CHliGHUFw==" }, + "studio/node_modules/proxy-compare": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.3.0.tgz", + "integrity": "sha512-c3L2CcAi7f7pvlD0D7xsF+2CQIW8C3HaYx2Pfgq8eA4HAl3GAH6/dVYsyBbYF/0XJs2ziGLrzmz5fmzPm6A0pQ==" + }, "studio/node_modules/react-resize-detector": { "version": "6.7.8", "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-6.7.8.tgz", @@ -37889,6 +37895,46 @@ "node": ">=4.2.0" } }, + "studio/node_modules/valtio": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.7.6.tgz", + "integrity": "sha512-zsGrCCYOIpy8egQxftduFyJusF/BMu3CganhHKUOE/I6t6V6yA1MDfZZkrYoWYCGkC3rSBYcIHEEsoYQM9lV2w==", + "dependencies": { + "proxy-compare": "2.3.0", + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@babel/helper-module-imports": ">=7.12", + "@babel/types": ">=7.13", + "aslemammad-vite-plugin-macro": ">=1.0.0-alpha.1", + "babel-plugin-macros": ">=3.0", + "react": ">=16.8", + "vite": ">=2.8.6" + }, + "peerDependenciesMeta": { + "@babel/helper-module-imports": { + "optional": true + }, + "@babel/types": { + "optional": true + }, + "aslemammad-vite-plugin-macro": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + }, + "react": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, "studio/node_modules/webpack-bundle-analyzer": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.3.0.tgz", @@ -63371,6 +63417,7 @@ "uniforms-bootstrap4": "^3.6.2", "uniforms-bridge-json-schema": "^3.6.2", "uuid": "^8.3.2", + "valtio": "*", "yup": "^0.32.11", "yup-password": "^0.2.2", "zxcvbn": "^4.4.2" @@ -63521,6 +63568,11 @@ "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-9.3.1.tgz", "integrity": "sha512-/Yvsd2D7miYB4HLJ3hOOS0+vnowQpaT75FsHzr/y5M9P4q9bwa7RcbW2YdH6KZBn8ceLbKGnHxMZ1CHliGHUFw==" }, + "proxy-compare": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.3.0.tgz", + "integrity": "sha512-c3L2CcAi7f7pvlD0D7xsF+2CQIW8C3HaYx2Pfgq8eA4HAl3GAH6/dVYsyBbYF/0XJs2ziGLrzmz5fmzPm6A0pQ==" + }, "react-resize-detector": { "version": "6.7.8", "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-6.7.8.tgz", @@ -63564,6 +63616,15 @@ "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", "dev": true }, + "valtio": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.7.6.tgz", + "integrity": "sha512-zsGrCCYOIpy8egQxftduFyJusF/BMu3CganhHKUOE/I6t6V6yA1MDfZZkrYoWYCGkC3rSBYcIHEEsoYQM9lV2w==", + "requires": { + "proxy-compare": "2.3.0", + "use-sync-external-store": "1.2.0" + } + }, "webpack-bundle-analyzer": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.3.0.tgz", diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 5f18c9dff3b..0f553810bc3 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -1,13 +1,14 @@ -import { FC, useCallback, useEffect, useRef, useState } from 'react' -import { IconLoader, Modal } from 'ui' +import HCaptcha from '@hcaptcha/react-hcaptcha' import { loadStripe } from '@stripe/stripe-js' import { Elements } from '@stripe/react-stripe-js' +import { FC, useCallback, useEffect, useState } from 'react' +import { Modal } from 'ui' import { useStore } from 'hooks' +import { useIsHCaptchaLoaded } from 'stores/hcaptcha-loaded-store' import { post } from 'lib/common/fetch' import { API_URL, STRIPE_PUBLIC_KEY } from 'lib/constants' import AddPaymentMethodForm from './AddPaymentMethodForm' -import HCaptcha from '@hcaptcha/react-hcaptcha' interface Props { visible: boolean @@ -21,7 +22,7 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = const { ui } = useStore() const [intent, setIntent] = useState() - const [captchaLoaded, setCaptchaLoaded] = useState(false) + const captchaLoaded = useIsHCaptchaLoaded() const [captchaToken, setCaptchaToken] = useState(null) const [captchaRef, setCaptchaRef] = useState(null) @@ -30,18 +31,6 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = setCaptchaRef(node) }, []) - /** - * We want to load the Stripe payment elements only after successfully verifying captcha. - * To execute hCaptcha when displaying the modal, we need to ensure hCaptcha is actually loaded. - * The `onLoad` method from hCaptcha is only executed ONCE for the entire application, so we cannot listen to it. - * One way to find out if hCaptcha has been initialized, is checking `window.hcaptcha`. When checking via window, - * we know if the hCaptcha has been initialized from a different part of the app or through this component. - */ - useEffect(() => { - setCaptchaLoaded(true) - // @ts-ignore - }, [window.hcaptcha]) - useEffect(() => { const loadPaymentForm = async () => { if (visible && captchaRef && captchaLoaded) { @@ -118,7 +107,7 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = onCancel={onLocalCancel} className="PAYMENT" > -
    +
    diff --git a/studio/package.json b/studio/package.json index bf108ae4eda..7a39cefd0d6 100644 --- a/studio/package.json +++ b/studio/package.json @@ -89,6 +89,7 @@ "uniforms-bootstrap4": "^3.6.2", "uniforms-bridge-json-schema": "^3.6.2", "uuid": "^8.3.2", + "valtio": "^1.7.6", "yup": "^0.32.11", "yup-password": "^0.2.2", "zxcvbn": "^4.4.2" diff --git a/studio/pages/_app.tsx b/studio/pages/_app.tsx index cd16894f14d..e77baa71827 100644 --- a/studio/pages/_app.tsx +++ b/studio/pages/_app.tsx @@ -27,6 +27,7 @@ import { useEffect, useState } from 'react' import { Hydrate, QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { RootStore } from 'stores' +import HCaptchaLoadedStore from 'stores/hcaptcha-loaded-store' import { StoreProvider } from 'hooks' import { GOTRUE_ERRORS } from 'lib/constants' import { auth } from 'lib/gotrue' @@ -115,6 +116,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { + diff --git a/studio/stores/hcaptcha-loaded-store.tsx b/studio/stores/hcaptcha-loaded-store.tsx new file mode 100644 index 00000000000..008053d20b0 --- /dev/null +++ b/studio/stores/hcaptcha-loaded-store.tsx @@ -0,0 +1,31 @@ +import HCaptcha from '@hcaptcha/react-hcaptcha' +import { proxy, useSnapshot } from 'valtio' + +const hCaptchaLoadedStoreState = proxy({ + loaded: false, + setLoaded() { + hCaptchaLoadedStoreState.loaded = true + }, +}) + +export const useIsHCaptchaLoaded = () => { + const snap = useSnapshot(hCaptchaLoadedStoreState) + + return snap.loaded +} + +const HCaptchaLoadedStore = () => { + const onLoad = () => { + hCaptchaLoadedStoreState.setLoaded() + } + + return ( + + ) +} + +export default HCaptchaLoadedStore From 773403b4ab6d6011f91ac1d228045504f1d5ee95 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 11:39:39 +0700 Subject: [PATCH 098/215] Change loading logic for when hcaptcha is involved in payment summary panel --- package-lock.json | 2 +- studio/components/interfaces/Billing/ProUpgrade.tsx | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 938bb21e236..d002b6210bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63417,7 +63417,7 @@ "uniforms-bootstrap4": "^3.6.2", "uniforms-bridge-json-schema": "^3.6.2", "uuid": "^8.3.2", - "valtio": "*", + "valtio": "^1.7.6", "yup": "^0.32.11", "yup-password": "^0.2.2", "zxcvbn": "^4.4.2" diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index f299c2308ce..2b3357df770 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -159,11 +159,16 @@ const ProUpgrade: FC = ({ } const onConfirmPayment = async () => { - let token = captchaToken + setIsSubmitting(true) + + const token = + captchaToken ?? (await captchaRef.current?.execute({ async: true }))?.response ?? undefined + if (!token) { - const captchaResponse = await captchaRef.current?.execute({ async: true }) - token = captchaResponse?.response ?? null + setIsSubmitting(false) + return } + const payload = formSubscriptionUpdatePayload( currentSubscription, selectedTier, @@ -173,8 +178,6 @@ const ProUpgrade: FC = ({ projectRegion, token ?? undefined ) - - setIsSubmitting(true) const res = await patch(`${API_URL}/projects/${projectRef}/subscription`, payload) resetCaptcha() From ebed18fcfe8f0a537a7f3bd6f091990d57322282 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 11:40:35 +0700 Subject: [PATCH 099/215] Change loading logic for when hcaptcha is involved in payment summary panel enterprise --- .../interfaces/Billing/EnterpriseUpdate.tsx | 12 +++++++----- studio/components/interfaces/Billing/ProUpgrade.tsx | 1 - 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index 77b10ab0809..ed599f91aa2 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -139,10 +139,14 @@ const EnterpriseUpdate: FC = ({ // Last todo to support enterprise billing on dashboard + E2E test const onConfirmPayment = async () => { - let token = captchaToken + setIsSubmitting(true) + + const token = + captchaToken ?? (await captchaRef.current?.execute({ async: true }))?.response ?? undefined + if (!token) { - const captchaResponse = await captchaRef.current?.execute({ async: true }) - token = captchaResponse?.response ?? null + setIsSubmitting(false) + return } const payload = { @@ -157,8 +161,6 @@ const EnterpriseUpdate: FC = ({ ), tier: currentSubscription.tier.price_id, } - - setIsSubmitting(true) const res = await patch(`${API_URL}/projects/${projectRef}/subscription`, payload) resetCaptcha() diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index 2b3357df770..a1a75ddc465 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -179,7 +179,6 @@ const ProUpgrade: FC = ({ token ?? undefined ) const res = await patch(`${API_URL}/projects/${projectRef}/subscription`, payload) - resetCaptcha() if (res?.error) { From a436163c96e51e17b810573b8e2e5df76f09da06 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 11:59:28 +0700 Subject: [PATCH 100/215] Add console log --- studio/components/interfaces/Billing/ProUpgrade.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index a1a75ddc465..e4eae47af07 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -164,6 +164,8 @@ const ProUpgrade: FC = ({ const token = captchaToken ?? (await captchaRef.current?.execute({ async: true }))?.response ?? undefined + console.log('confirm', { token }) + if (!token) { setIsSubmitting(false) return From b57fc12da86da9385b05b823a1b3e3cf73199961 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 12:09:00 +0700 Subject: [PATCH 101/215] Add more logs --- .../components/interfaces/Billing/ProUpgrade.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index e4eae47af07..d30584ef87c 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -160,17 +160,21 @@ const ProUpgrade: FC = ({ const onConfirmPayment = async () => { setIsSubmitting(true) + let token = captchaToken - const token = - captchaToken ?? (await captchaRef.current?.execute({ async: true }))?.response ?? undefined - - console.log('confirm', { token }) - - if (!token) { + try { + if (!token) { + const captchaResponse = await captchaRef.current?.execute({ async: true }) + token = captchaResponse?.response ?? null + } + } catch (error) { + console.log('ERRORR', error) setIsSubmitting(false) return } + console.log('TOKEN', { token }) + const payload = formSubscriptionUpdatePayload( currentSubscription, selectedTier, From 0d5c1556a61d0a9a250e70113c2adda3be69113e Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 12:42:34 +0700 Subject: [PATCH 102/215] Fix hcaptcha loading for proupgrade and enterprise update --- .../interfaces/Billing/EnterpriseUpdate.tsx | 11 +++++++---- studio/components/interfaces/Billing/ProUpgrade.tsx | 3 --- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index ed599f91aa2..078cd190f42 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -140,11 +140,14 @@ const EnterpriseUpdate: FC = ({ // Last todo to support enterprise billing on dashboard + E2E test const onConfirmPayment = async () => { setIsSubmitting(true) + let token = captchaToken - const token = - captchaToken ?? (await captchaRef.current?.execute({ async: true }))?.response ?? undefined - - if (!token) { + try { + if (!token) { + const captchaResponse = await captchaRef.current?.execute({ async: true }) + token = captchaResponse?.response ?? null + } + } catch (error) { setIsSubmitting(false) return } diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index d30584ef87c..e7f246d8b27 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -168,13 +168,10 @@ const ProUpgrade: FC = ({ token = captchaResponse?.response ?? null } } catch (error) { - console.log('ERRORR', error) setIsSubmitting(false) return } - console.log('TOKEN', { token }) - const payload = formSubscriptionUpdatePayload( currentSubscription, selectedTier, From cdbc41440568560cbbc8ee80bbb16f5e491b5df3 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 13:01:39 +0700 Subject: [PATCH 103/215] Update ext survey hcaptcha --- .../AddNewPaymentMethodModal.tsx | 11 +++++-- .../Billing/ExitSurvey/ExitSurvey.tsx | 29 ++++++++++++------- .../[ref]/settings/billing/update/free.tsx | 2 +- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 0f553810bc3..f8c52edd2eb 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -35,9 +35,14 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = const loadPaymentForm = async () => { if (visible && captchaRef && captchaLoaded) { let token = captchaToken - if (!token) { - const captchaResponse = await captchaRef.execute({ async: true }) - token = captchaResponse?.response ?? null + + try { + if (!token) { + const captchaResponse = await captchaRef.execute({ async: true }) + token = captchaResponse?.response ?? null + } + } catch (error) { + return } await setupIntent(token ?? undefined) diff --git a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx index 52ad8ea889e..35232ef2b7f 100644 --- a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx +++ b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx @@ -25,8 +25,8 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { const projectId = ui.selectedProject?.id ?? -1 const projectRef = ui.selectedProject?.ref - const [captchaToken, setCaptchaToken] = useState(null) const captchaRef = useRef(null) + const [captchaToken, setCaptchaToken] = useState(null) const formRef = useRef() @@ -92,11 +92,24 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { }) } + setIsSubmitting(true) + let token = captchaToken + + try { + if (!token) { + const captchaResponse = await captchaRef.current?.execute({ async: true }) + token = captchaResponse?.response ?? null + } + } catch (error) { + setIsSubmitting(false) + return + } + if (willChangeComputeSize) { setMessage(values.message) return setShowConfirmModal(true) } else { - downgradeProject(values) + downgradeProject(values, token as string) } } @@ -105,15 +118,9 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { captchaRef.current?.resetCaptcha() } - const downgradeProject = async (values?: any) => { + const downgradeProject = async (values?: any, hcaptchaToken?: string) => { const downgradeMessage = values?.message ?? message - let token = captchaToken - if (!token) { - const captchaResponse = await captchaRef.current?.execute({ async: true }) - token = captchaResponse?.response ?? null - } - try { setIsSubmitting(true) @@ -125,7 +132,7 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { tier, addons, proration_date, - hcaptchaToken: token ?? undefined, + hcaptchaToken: captchaToken ?? hcaptchaToken, }) resetCaptcha() @@ -191,7 +198,7 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { enterFrom="transform opacity-0 translate-x-10" enterTo="transform opacity-100 translate-x-0" > -
    +
    - -
    -
    -
    - - ) -} From 6ed0299844d8b3d3caad7e60dfd18c8eba0a5c55 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Tue, 20 Dec 2022 17:28:14 -0330 Subject: [PATCH 106/215] Fixes unique keys warning --- apps/docs/components/Navigation/Footer.tsx | 4 ++-- .../docs/components/Navigation/NavigationMenu/HomeMenu.tsx | 4 ++-- .../Navigation/NavigationMenu/NavigationMenu.tsx | 4 ++-- spec/common-self-hosting-storage-sections.json | 7 ------- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/apps/docs/components/Navigation/Footer.tsx b/apps/docs/components/Navigation/Footer.tsx index 3d85d66b3cb..cbbb215e7a6 100644 --- a/apps/docs/components/Navigation/Footer.tsx +++ b/apps/docs/components/Navigation/Footer.tsx @@ -14,7 +14,7 @@ const Footer = () => { Supabase 2022 — {footerData.map((item) => ( - + {item.title} ))} @@ -41,7 +41,7 @@ const Footer = () => { diff --git a/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx index e6f5e8ea4be..d4167720768 100644 --- a/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx @@ -129,7 +129,7 @@ const NavigationMenuHome = () => {
      {home.map((section, sectionIndex) => { return ( - <> +
      {sectionIndex !== 0 && (
      { })}
      - +
    ) })} diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx index b8ea7efeab4..96bb2416bcf 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx @@ -173,7 +173,7 @@ const SideNav = () => { {/* // Client Libs */} { allowedClientKeys={generateAllowedClientLibKeys(libCommonSections, spec_dart_v0)} /> Date: Tue, 20 Dec 2022 17:28:30 -0330 Subject: [PATCH 107/215] Exclude Edit this Page button --- apps/docs/layouts/guides/index.tsx | 30 ++++++++++++++++++------------ apps/docs/pages/index.mdx | 2 +- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/apps/docs/layouts/guides/index.tsx b/apps/docs/layouts/guides/index.tsx index 25b26b3fec7..e3b64828a60 100644 --- a/apps/docs/layouts/guides/index.tsx +++ b/apps/docs/layouts/guides/index.tsx @@ -31,10 +31,11 @@ const Layout: FC = (props) => { const [tocList, setTocList] = useState([]) const { asPath } = useRouter() - const page = getPageType(asPath) const router = useRouter() + const EDIT_BUTTON_EXCLUDE_LIST = ['/404'] + useEffect(() => { if (hash && tocList.length > 0) { highlightSelectedTocItem(hash as string) @@ -100,18 +101,23 @@ const Layout: FC = (props) => { )}
    {props.children} -
    -
    - - - Edit this page on GitHub - - + + {EDIT_BUTTON_EXCLUDE_LIST.includes(router.route) ? ( + <> + ) : ( + -
    + )}
    {!props.hideToc && hasTableOfContents && !props.meta?.hide_table_of_contents && ( diff --git a/apps/docs/pages/index.mdx b/apps/docs/pages/index.mdx index 5a7e2f17b28..9921286f50c 100644 --- a/apps/docs/pages/index.mdx +++ b/apps/docs/pages/index.mdx @@ -30,7 +30,7 @@ export const meta = { ">
    -
    +

    Getting Started

    Discover how to set up a database to an app making queries in just a few minutes. From 728cb7b86aa4baeac47e59f81602095cac7c15d4 Mon Sep 17 00:00:00 2001 From: Long Hoang Date: Wed, 21 Dec 2022 11:48:26 +0700 Subject: [PATCH 108/215] Update link to new careers page --- apps/docs/public/humans.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index 95817e22f6d..a403c683c7c 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -60,7 +60,7 @@ ____________ JOBS -If you are interested in working with us check out the open roles here: https://about.supabase.com/careers +If you are interested in working with us check out the open roles here: https://supabase.com/careers ___________ From a5632d5d7317df86feefeb9fd54ec7dcd9e3e1b1 Mon Sep 17 00:00:00 2001 From: Long Hoang Date: Wed, 21 Dec 2022 11:48:43 +0700 Subject: [PATCH 109/215] Add Long to humans.txt --- apps/docs/public/humans.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index a403c683c7c..2231880862b 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -34,6 +34,7 @@ Kevin GrĂĽneberg Lakshmipathi G Laura C Leo T +Long Hoang Margarita Sandomirskaia Marijana Ĺ imag Mark Burggraf From 70aac81cec8c3c97861b64c0e5edfc676c2e7663 Mon Sep 17 00:00:00 2001 From: dannykng Date: Tue, 20 Dec 2022 16:55:35 -0800 Subject: [PATCH 110/215] Update Developers links --- apps/www/data/Developers.json | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/apps/www/data/Developers.json b/apps/www/data/Developers.json index a29460f0c5b..df449e784ab 100644 --- a/apps/www/data/Developers.json +++ b/apps/www/data/Developers.json @@ -1,26 +1,20 @@ [ { "text": "Documentation", - "description": "Everything you need that makes it simple to get started.", + "description": "Everything you need to start building with Supabase.", "url": "/docs", "icon": "M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" }, { - "text": "API reference", - "description": "Examples and references for using API libraries.", - "url": "/docs/client/supabase-client", - "icon": "M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" - }, - { - "text": "Guides", - "description": "Examples and references for using Supabase.", - "url": "/docs/", + "text": "Changelog", + "description": "See the latest updates and product improvements.", + "url": "/changelog", "icon": "M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" }, { "text": "Careers", "description": "Join the Supabase team and get involved.", - "url": "https://supabase.com/careers", + "url": "/careers", "icon": "M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" } ] From 70b66c916f177090976f9b0677c64ea02b60ba15 Mon Sep 17 00:00:00 2001 From: dng Date: Tue, 20 Dec 2022 16:29:56 -0800 Subject: [PATCH 111/215] Remove redirect --- apps/www/lib/redirects.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js index 09317c8a31f..ff18f604298 100644 --- a/apps/www/lib/redirects.js +++ b/apps/www/lib/redirects.js @@ -1787,16 +1787,6 @@ module.exports = [ source: '/docs/guides/with-ionic-angular', destination: '/docs/guides/getting-started/tutorials/with-ionic-angular', }, - { - permanent: true, - source: '/docs/guides/examples', - destination: '/docs/guides/resources/examples', - }, - { - permanent: true, - source: '/docs/guides/examples', - destination: '/docs/guides/resources/examples', - }, { permanent: true, source: '/docs/guides/hosting/overview', From 32a3ec5f7e856e7dd190debdd348af975c00dfc3 Mon Sep 17 00:00:00 2001 From: Ollie Halliday <22655069+Hallidayo@users.noreply.github.com> Date: Tue, 20 Dec 2022 16:39:27 +0000 Subject: [PATCH 112/215] added on delete cascade --- studio/components/interfaces/SQLEditor/SQLEditor.constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/interfaces/SQLEditor/SQLEditor.constants.ts b/studio/components/interfaces/SQLEditor/SQLEditor.constants.ts index 63ec9612ed2..de1990ca96b 100644 --- a/studio/components/interfaces/SQLEditor/SQLEditor.constants.ts +++ b/studio/components/interfaces/SQLEditor/SQLEditor.constants.ts @@ -812,7 +812,7 @@ create publication supabase_realtime sql: ` -- Create a table for public profiles create table profiles ( - id uuid references auth.users not null primary key, + id uuid references auth.users on delete cascade not null primary key, updated_at timestamp with time zone, username text unique, full_name text, From 16c7c42c5a6b2367f82efd35bd7f93dddd202b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mauricio=20Ordo=C3=B1ez?= Date: Wed, 21 Dec 2022 04:18:19 -0500 Subject: [PATCH 113/215] Update README.es.md --- i18n/README.es.md | 139 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 128 insertions(+), 11 deletions(-) diff --git a/i18n/README.es.md b/i18n/README.es.md index a653d1b2492..602393663a2 100644 --- a/i18n/README.es.md +++ b/i18n/README.es.md @@ -71,17 +71,134 @@ Supabase es una [plataforma alojada](https://app.supabase.com). Te puedes regist Nuestra librerĂ­a de cliente es modular. Cada sub-librerĂ­a es una implementaciĂłn independiente para cada sistema externo. Esta es una de las formas de apoyar las herramientas existentes. -- **`supabase-{lang}`**: Combina librerĂ­as y agrega mejoras. - - `postgrest-{lang}`: LibrerĂ­a de cliente para trabajar con [PostgREST](https://github.com/postgrest/postgrest) - - `realtime-{lang}`: LibrerĂ­a de cliente para trabajar con [Realtime](https://github.com/supabase/realtime) - - `gotrue-{lang}`: LibrerĂ­a de cliente para trabajar con [GoTrue](https://github.com/netlify/gotrue) - -| Repositorio | Oficial | Comunidad | -| --------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`supabase-{lang}`** | [`JS`](https://github.com/supabase/supabase-js) | [`C#`](https://github.com/supabase/supabase-csharp) \| [`Flutter`](https://github.com/supabase/supabase-flutter) \| [`Python`](https://github.com/supabase/supabase-py) \| `Rust` | -| `postgrest-{lang}` | [`JS`](https://github.com/supabase/postgrest-js) | [`C#`](https://github.com/supabase/postgrest-csharp) \| [`Dart`](https://github.com/supabase/postgrest-dart) \| [`Python`](https://github.com/supabase/postgrest-py) \| [`Rust`](https://github.com/supabase/postgrest-rs) | -| `realtime-{lang}` | [`JS`](https://github.com/supabase/realtime-js) | [`C#`](https://github.com/supabase/realtime-csharp) \| [`Dart`](https://github.com/supabase/realtime-dart) \| [`Python`](https://github.com/supabase/realtime-py) \| `Rust` | -| `gotrue-{lang}` | [`JS`](https://github.com/supabase/gotrue-js) | [`C#`](https://github.com/supabase/gotrue-csharp) \| [`Dart`](https://github.com/supabase/gotrue-dart) \| [`Python`](https://github.com/supabase/gotrue-py) \| `Rust` | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    LenguajeClienteCaracterĂ­stica - Clientes (incluido en el cliente de Supabase)
    SupabasePostgRESTGoTrueRealtimeStorageFunctions
    ⚡️ Oficial ⚡️
    JavaScript (TypeScript)supabase-jspostgrest-jsgotrue-jsrealtime-jsstorage-jsfunctions-js
    Fluttersupabase-flutterpostgrest-dartgotrue-dartrealtime-dartstorage-dartfunctions-dart
    đź’š Comunidad đź’š
    C#supabase-csharppostgrest-csharpgotrue-csharprealtime-csharpstorage-csharpfunctions-csharp
    Go-postgrest-gogotrue-go-storage-gofunctions-go
    Java--gotrue-java-storage-java-
    Kotlinsupabase-ktpostgrest-ktgotrue-ktrealtime-ktstorage-ktfunctions-kt
    Pythonsupabase-pypostgrest-pygotrue-pyrealtime-pystorage-pyfunctions-py
    Rubysupabase-rbpostgrest-rb----
    Rust-postgrest-rs----
    Swiftsupabase-swiftpostgrest-swiftgotrue-swiftrealtime-swiftstorage-swiftfunctions-swift
    Godot Engine (GDScript)supabase-gdscriptpostgrest-gdscriptgotrue-gdscriptrealtime-gdscriptstorage-gdscriptfunctions-gdscript
    From eb6cffb09aa7e3ba1dc3ebcb112a2300644cd48f Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 16:35:42 +0700 Subject: [PATCH 114/215] Hide schemas with pg_ prefix --- studio/stores/pgmeta/MetaStore.ts | 1 - studio/stores/pgmeta/SchemaStore.ts | 8 ++++++++ studio/stores/pgmeta/TriggersStore.ts | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/studio/stores/pgmeta/MetaStore.ts b/studio/stores/pgmeta/MetaStore.ts index a8ed860a318..eef317f1397 100644 --- a/studio/stores/pgmeta/MetaStore.ts +++ b/studio/stores/pgmeta/MetaStore.ts @@ -154,7 +154,6 @@ export default class MetaStore implements IMetaStore { 'extensions', 'information_schema', 'net', - 'pg_catalog', 'pgsodium', 'pgsodium_masks', 'pgbouncer', diff --git a/studio/stores/pgmeta/SchemaStore.ts b/studio/stores/pgmeta/SchemaStore.ts index 7a0082db225..1fd1ff89ac5 100644 --- a/studio/stores/pgmeta/SchemaStore.ts +++ b/studio/stores/pgmeta/SchemaStore.ts @@ -27,6 +27,14 @@ export default class SchemaStore extends PostgresMetaInterface { this.views = [] } + // Dashboard to hide schemas with pg_ prefix + list(filter: any) { + const schemasFilter = (schema: PostgresSchema) => + !schema.name.startsWith('pg_') && (typeof filter === 'function' ? filter(schema) : true) + + return super.list(schemasFilter) + } + async loadViews(schema: string) { const query = ` select schemaname as schema, viewname as name from pg_catalog.pg_views diff --git a/studio/stores/pgmeta/TriggersStore.ts b/studio/stores/pgmeta/TriggersStore.ts index 4d83608c885..91631e94f7f 100644 --- a/studio/stores/pgmeta/TriggersStore.ts +++ b/studio/stores/pgmeta/TriggersStore.ts @@ -20,8 +20,8 @@ export default class TriggersStore extends PostgresMetaInterface { */ list(filter: any) { const triggersFilter = (h: any) => - h.function_schema != 'supabase_functions' && - h.schema != 'net' && + h.function_schema !== 'supabase_functions' && + h.schema !== 'net' && (typeof filter === 'function' ? filter(h) : true) return super.list(triggersFilter) From 91f43c783faf43d09f0d2af40c68adef8c8064a8 Mon Sep 17 00:00:00 2001 From: Ollie Halliday <22655069+Hallidayo@users.noreply.github.com> Date: Wed, 21 Dec 2022 10:37:18 +0000 Subject: [PATCH 115/215] changed url links in js and dart --- spec/supabase_dart_v1.yml | 6 +++--- spec/supabase_js_v2.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/supabase_dart_v1.yml b/spec/supabase_dart_v1.yml index 073ec4d6f10..304d6988077 100644 --- a/spec/supabase_dart_v1.yml +++ b/spec/supabase_dart_v1.yml @@ -21,13 +21,13 @@ functions: description: | Creates a new user. notes: | - - By default, the user needs to verify their email address before logging in. To turn this off, disable **Confirm email** in [your project](https://app.supabase.com/project/_/auth/settings). + - By default, the user needs to verify their email address before logging in. To turn this off, disable **Confirm email** in [your project](https://app.supabase.com/project/_/auth/providers). - **Confirm email** determines if users need to confirm their email address after signing up. - If **Confirm email** is enabled, a `user` is returned but `session` is null. - If **Confirm email** is disabled, both a `user` and a `session` are returned. - - When the user confirms their email address, they are redirected to the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url) by default. You can modify your `SITE_URL` or add additional redirect URLs in [your project](https://app.supabase.com/project/_/auth/settings). + - When the user confirms their email address, they are redirected to the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url) by default. You can modify your `SITE_URL` or add additional redirect URLs in [your project](https://app.supabase.com/project/_/auth/url-configuration). - If signUp() is called for an existing confirmed user: - - If **Confirm email** is enabled in [your project](https://app.supabase.com/project/_/auth/settings), an obfuscated/fake user object is returned. + - If **Confirm email** is enabled in [your project](https://app.supabase.com/project/_/auth/providers), an obfuscated/fake user object is returned. - If **Confirm email** is disabled, the error message, `User already registered` is returned. examples: - id: sign-up diff --git a/spec/supabase_js_v2.yml b/spec/supabase_js_v2.yml index 6bc7533f139..8d13fe5346f 100644 --- a/spec/supabase_js_v2.yml +++ b/spec/supabase_js_v2.yml @@ -136,13 +136,13 @@ functions: title: 'signUp()' $ref: '@supabase/gotrue-js.GoTrueClient.signUp' notes: | - - By default, the user needs to verify their email address before logging in. To turn this off, disable **Confirm email** in [your project](https://app.supabase.com/project/_/auth/settings). + - By default, the user needs to verify their email address before logging in. To turn this off, disable **Confirm email** in [your project](https://app.supabase.com/project/_/auth/providers). - **Confirm email** determines if users need to confirm their email address after signing up. - If **Confirm email** is enabled, a `user` is returned but `session` is null. - If **Confirm email** is disabled, both a `user` and a `session` are returned. - - When the user confirms their email address, they are redirected to the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url) by default. You can modify your `SITE_URL` or add additional redirect URLs in [your project](https://app.supabase.com/project/_/auth/settings). + - When the user confirms their email address, they are redirected to the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url) by default. You can modify your `SITE_URL` or add additional redirect URLs in [your project](https://app.supabase.com/project/_/auth/url-configuration). - If signUp() is called for an existing confirmed user: - - If **Confirm email** is enabled in [your project](https://app.supabase.com/project/_/auth/settings), an obfuscated/fake user object is returned. + - If **Confirm email** is enabled in [your project](https://app.supabase.com/project/_/auth/providers), an obfuscated/fake user object is returned. - If **Confirm email** is disabled, the error message, `User already registered` is returned. - To fetch the currently logged-in user, refer to [`getUser()`](/docs/reference/javascript/auth-getuser). examples: From d8bc12f9d6edb67f798c04fdc97db402a4229401 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Wed, 21 Dec 2022 11:50:14 +0000 Subject: [PATCH 116/215] fix: pro upgrade show captcha before confirm modal --- .../PaymentSummaryPanel.tsx | 16 ++++++++----- .../interfaces/Billing/ProUpgrade.tsx | 24 +++++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx b/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx index 9e0db842011..8f648296f80 100644 --- a/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx +++ b/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx @@ -42,6 +42,7 @@ interface Props { isLoadingPaymentMethods: boolean onSelectPaymentMethod: (method: any) => void onSelectAddNewPaymentMethod: () => void + beforeConfirmPayment?: () => Promise onConfirmPayment: () => void isSubmitting: boolean @@ -72,6 +73,7 @@ const PaymentSummaryPanel: FC = ({ isLoadingPaymentMethods, onSelectPaymentMethod, onSelectAddNewPaymentMethod, + beforeConfirmPayment, onConfirmPayment, isSubmitting, captcha, @@ -132,6 +134,8 @@ const PaymentSummaryPanel: FC = ({ message: error, }) } else { + await beforeConfirmPayment?.() + isChangingComputeSize || (isChangingPITRDuration && selectedPITRDays === 0) ? setShowConfirmModal(true) : onConfirmPayment() @@ -141,7 +145,7 @@ const PaymentSummaryPanel: FC = ({ return ( <>

    Payment Summary

    @@ -296,7 +300,7 @@ const PaymentSummaryPanel: FC = ({
    )} -
    +
    = ({

    Select payment method

    {isLoadingPaymentMethods ? ( -
    +

    Retrieving payment methods

    ) : paymentMethods.length === 0 ? ( -
    +

    No saved payment methods

    @@ -373,11 +377,11 @@ const PaymentSummaryPanel: FC = ({ ) })}
    -

    +

    Add new payment method

    diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index e7f246d8b27..9f3ef740bcc 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -158,7 +158,7 @@ const ProUpgrade: FC = ({ captchaRef.current?.resetCaptcha() } - const onConfirmPayment = async () => { + const beforeConfirmPayment = async () => { setIsSubmitting(true) let token = captchaToken @@ -166,12 +166,15 @@ const ProUpgrade: FC = ({ if (!token) { const captchaResponse = await captchaRef.current?.execute({ async: true }) token = captchaResponse?.response ?? null + setCaptchaToken(token) } } catch (error) { setIsSubmitting(false) return } + } + const onConfirmPayment = async () => { const payload = formSubscriptionUpdatePayload( currentSubscription, selectedTier, @@ -179,7 +182,7 @@ const ProUpgrade: FC = ({ nonChangeableAddons, selectedPaymentMethodId, projectRegion, - token ?? undefined + captchaToken ?? undefined ) const res = await patch(`${API_URL}/projects/${projectRef}/subscription`, payload) resetCaptcha() @@ -223,17 +226,17 @@ const ProUpgrade: FC = ({ enter="transition ease-out duration-300" enterFrom="transform opacity-0 translate-x-10" enterTo="transform opacity-100 translate-x-0" - className="flex w-full items-start justify-between" + className="flex items-start justify-between w-full" >
    -
    +
    onSelectBack()} />

    Change your project's subscription

    @@ -257,14 +260,14 @@ const ProUpgrade: FC = ({ )}
    -
    +

    Enable spend cap

    setShowSpendCapHelperModal(true)} />
    @@ -342,6 +345,7 @@ const ProUpgrade: FC = ({ onSelectAddNewPaymentMethod={() => { setShowAddPaymentMethodModal(true) }} + beforeConfirmPayment={beforeConfirmPayment} onConfirmPayment={onConfirmPayment} isSubmitting={isSubmitting} captcha={ @@ -375,7 +379,7 @@ const ProUpgrade: FC = ({ header="Enabling spend cap" onCancel={() => setShowSpendCapHelperModal(false)} > -
    +

    @@ -390,7 +394,7 @@ const ProUpgrade: FC = ({

    {/* Maybe instead of a table, show something more interactive like a spend cap playground */} {/* Maybe ideate this in Figma first but this is good enough for now */} -
    +

    Item

    Limit

    @@ -417,7 +421,7 @@ const ProUpgrade: FC = ({ From 4b765fd1250379ac1cf47def855b8293fe4633af Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 21:30:18 +0700 Subject: [PATCH 117/215] Fix hcaptcha for upgrading instance --- .../interfaces/Billing/EnterpriseUpdate.tsx | 19 +++++++++++++++++++ .../PaymentSummaryPanel.tsx | 17 +++++++++++------ .../interfaces/Billing/ProUpgrade.tsx | 6 ++++-- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index 078cd190f42..697060a6340 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -137,6 +137,24 @@ const EnterpriseUpdate: FC = ({ captchaRef.current?.resetCaptcha() } + const beforeConfirmPayment = async (): Promise => { + setIsSubmitting(true) + let token = captchaToken + + try { + if (!token) { + const captchaResponse = await captchaRef.current?.execute({ async: true }) + token = captchaResponse?.response ?? null + setCaptchaToken(token) + } + } catch (error) { + setIsSubmitting(false) + return false + } + + return true + } + // Last todo to support enterprise billing on dashboard + E2E test const onConfirmPayment = async () => { setIsSubmitting(true) @@ -296,6 +314,7 @@ const EnterpriseUpdate: FC = ({ onSelectAddNewPaymentMethod={() => { setShowAddPaymentMethodModal(true) }} + beforeConfirmPayment={beforeConfirmPayment} onConfirmPayment={onConfirmPayment} captcha={ void onSelectAddNewPaymentMethod: () => void - beforeConfirmPayment?: () => Promise + beforeConfirmPayment: () => Promise onConfirmPayment: () => void isSubmitting: boolean @@ -134,11 +134,16 @@ const PaymentSummaryPanel: FC = ({ message: error, }) } else { - await beforeConfirmPayment?.() - - isChangingComputeSize || (isChangingPITRDuration && selectedPITRDays === 0) - ? setShowConfirmModal(true) - : onConfirmPayment() + // [Joshen] We're validating captcha before subsequent actions as there's an issue + // with the hcaptcha overlay and the modal component, such that clicking on the hcaptcha closes + // both the hcaptcha overlay and the modal. Need to figure that out first, then we can validate + // the hcaptcha within onConfirmPayment() + const hasValidCaptcha = await beforeConfirmPayment() + if (hasValidCaptcha) { + isChangingComputeSize || (isChangingPITRDuration && selectedPITRDays === 0) + ? setShowConfirmModal(true) + : onConfirmPayment() + } } } diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index 9f3ef740bcc..58434629eb8 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -158,7 +158,7 @@ const ProUpgrade: FC = ({ captchaRef.current?.resetCaptcha() } - const beforeConfirmPayment = async () => { + const beforeConfirmPayment = async (): Promise => { setIsSubmitting(true) let token = captchaToken @@ -170,8 +170,10 @@ const ProUpgrade: FC = ({ } } catch (error) { setIsSubmitting(false) - return + return false } + + return true } const onConfirmPayment = async () => { From 4b391ae42691348c344781da96902815f5da631f Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 21:44:01 +0700 Subject: [PATCH 118/215] Fix submitting state --- studio/components/interfaces/Billing/EnterpriseUpdate.tsx | 1 + studio/components/interfaces/Billing/ProUpgrade.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index 697060a6340..254d83275c7 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -152,6 +152,7 @@ const EnterpriseUpdate: FC = ({ return false } + setIsSubmitting(false) return true } diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index 58434629eb8..2f05f9a8844 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -173,6 +173,7 @@ const ProUpgrade: FC = ({ return false } + setIsSubmitting(false) return true } From b40e6216aba6ec03574318b23a45b7648333e529 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 16:49:34 +0700 Subject: [PATCH 119/215] Fix storage side bar text overflow --- studio/components/layouts/StorageLayout/StorageMenu.tsx | 6 ++++-- .../Storage/StorageExplorer/FileExplorerHeader.js | 3 ++- studio/components/ui/ProductMenu/ProductMenuItem.tsx | 8 ++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/studio/components/layouts/StorageLayout/StorageMenu.tsx b/studio/components/layouts/StorageLayout/StorageMenu.tsx index dc41cdacb64..2889292f42f 100644 --- a/studio/components/layouts/StorageLayout/StorageMenu.tsx +++ b/studio/components/layouts/StorageLayout/StorageMenu.tsx @@ -122,8 +122,10 @@ const BucketRow = ({ -

    {bucket.name}

    +
    +

    + {bucket.name} +

    {bucket.public && Public}
    } diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeader.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeader.js index 139a0be29cb..ad57ec629c1 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeader.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeader.js @@ -34,7 +34,7 @@ const HeaderPathEdit = ({ loading, breadcrumbs, togglePathEdit }) => {
    ) : (
    -

    {breadcrumbs[breadcrumbs.length - 1] || ''}

    +

    {breadcrumbs[breadcrumbs.length - 1] || ''}

    + ) })} From 36f4b192385396cdf3437066919d4aef92672983 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 21 Dec 2022 16:26:16 -0330 Subject: [PATCH 121/215] Rename component so it matches filename --- apps/docs/layouts/SiteLayout.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/docs/layouts/SiteLayout.tsx b/apps/docs/layouts/SiteLayout.tsx index 9c73280091d..ac46ec931fc 100644 --- a/apps/docs/layouts/SiteLayout.tsx +++ b/apps/docs/layouts/SiteLayout.tsx @@ -170,7 +170,7 @@ const SiteLayout = ({ children }) => {
    - +
    @@ -183,7 +183,6 @@ const SiteLayout = ({ children }) => { ].join(' ')} >
    - {/* */}
    @@ -269,7 +268,7 @@ const SiteLayout = ({ children }) => { : 'left-[-40px] h-0 opacity-0 invisible', ].join(' ')} > - +
    From 5aa0f828ef0d3cd3b0687f510acbc5c784b47316 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 21 Dec 2022 16:26:54 -0330 Subject: [PATCH 122/215] Pass a menu context to avoid dupe key warnings --- .../NavigationMenu/NavigationMenu.tsx | 26 +++++++++---------- .../NavigationMenuGuideList.tsx | 25 +++++++++--------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx index 96bb2416bcf..c579eb68707 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx @@ -62,7 +62,7 @@ export type RefKeyOptions = | 'self-hosting-storage' | 'self-hosting-realtime' -const SideNav = () => { +const NavigationMenu = ({ context }) => { const router = useRouter() let version = '' @@ -160,17 +160,17 @@ const SideNav = () => {
    {/* // main menu */} - - - - - - - - - - - + + + + + + + + + + + {/* // Client Libs */} { ) } -export default memo(SideNav) +export default memo(NavigationMenu) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx index 6b110bd5687..d5f97f50e6c 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx @@ -3,7 +3,7 @@ import { useTheme } from 'common/Providers' import Image from 'next/image' import Link from 'next/link' import { useRouter } from 'next/router' -import React from 'react' +import React, { Fragment } from 'react' import { IconChevronLeft } from '~/../../packages/ui' import { useMenuLevelId } from '~/hooks/useMenuState' import * as NavItems from './NavigationMenu.constants' @@ -11,8 +11,9 @@ import * as NavItems from './NavigationMenu.constants' interface Props { id: string setMenuLevelId?: any + context: 'side' | 'mobile' } -const NavigationMenuGuideList: React.FC = ({ id, setMenuLevelId }) => { +const NavigationMenuGuideList: React.FC = ({ id, setMenuLevelId, context }) => { const router = useRouter() const { isDarkMode } = useTheme() @@ -30,7 +31,7 @@ const NavigationMenuGuideList: React.FC = ({ id, setMenuLevelId }) => { return ( = ({ id, setMenuLevelId }) => { // console.log('1st type of link?', x.items && x.items.length > 0) // console.log() return ( -
    +
    {x.items && x.items.length > 0 ? (
    {x.items.map((subItem, subItemIndex) => { @@ -114,7 +115,7 @@ const NavigationMenuGuideList: React.FC = ({ id, setMenuLevelId }) => { } return ( - <> + {subItemIndex === 0 && ( <>
    @@ -123,8 +124,8 @@ const NavigationMenuGuideList: React.FC = ({ id, setMenuLevelId }) => { )} - -
  • + +
  • = ({ id, setMenuLevelId }) => { {subItem.items.map((subSubItem, subSubItemIndex) => { return ( -
  • +
  • = ({ id, setMenuLevelId }) => { )} - + ) })}
  • ) : ( - <> +
  • = ({ id, setMenuLevelId }) => {
  • - + )}
    ) @@ -210,7 +211,7 @@ const NavigationMenuGuideList: React.FC = ({ id, setMenuLevelId }) => { )} {menu.extras?.map((x) => { return ( -
    +
  • From 40139296c2856ba06328d62f1b5297c4ecf4b3f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Wed, 21 Dec 2022 20:12:07 +0000 Subject: [PATCH 123/215] LW6 internal linking. --- .../_blog/2022-12-09-launch-week-6-hackathon.mdx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/www/_blog/2022-12-09-launch-week-6-hackathon.mdx b/apps/www/_blog/2022-12-09-launch-week-6-hackathon.mdx index 212e9cce132..5401d567ae6 100644 --- a/apps/www/_blog/2022-12-09-launch-week-6-hackathon.mdx +++ b/apps/www/_blog/2022-12-09-launch-week-6-hackathon.mdx @@ -113,3 +113,16 @@ Don't forget to checkout the new feature's being announced as part of [Launch We - Any intellectual property developed during the hackathon will belong to the team that developed it. We expect that each team will have an agreement between themselves regarding the IP, but this is not required - By making a submission you grant Supabase permission to use screenshots, code-snippets and/or links to your project or content of your README on our Twitter, blog, website, email updates, and in the Supabase discord server. Supabase does not make any claims over your IP. - $1500 prize will be paid by making a GitHub sponsorship to the winning repo, $500/month for 3 months. The goal here is to create a sustainable project that will be continue to be maintained for the duration of the sponsorship period (and hopefully beyond :) ) + +## More Launch Week 6 + +- [Day 1: New Supabase Docs, built with Next.js](https://supabase.com/blog/new-supabase-docs-built-with-nextjs) +- [Day 2: Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn) +- [Day 3: Multi-factor Authentication via Row Level Security Enforcement](https://supabase.com/blog/mfa-auth-via-rls) +- [Day 4: Supabase Wrappers, a Postgres FDW framework written in Rust](https://supabase.com/blog/postgres-foreign-data-wrappers-rust) +- [Day 5: Supabase Vault is now in Beta](https://supabase.com/blog/vault-now-in-beta) +- [Community Day](https://supabase.com/blog/launch-week-6-community-day) +- [Point in Time Recovery is now available](https://supabase.com/blog/postgres-point-in-time-recovery) +- [Custom Domain Names are now available](https://supabase.com/blog/custom-domain-names) +- [Wrap Up: everything we shipped](https://supabase.com/blog/launch-week-6-wrap-up) + From 92e0798d707e209e05f5cf565a1de8e7f5de44ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Wed, 21 Dec 2022 20:13:13 +0000 Subject: [PATCH 124/215] Add LW6 links. --- apps/www/_blog/2022-12-10-postgres-crdt.mdx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/www/_blog/2022-12-10-postgres-crdt.mdx b/apps/www/_blog/2022-12-10-postgres-crdt.mdx index 066544d0003..143a356fee7 100644 --- a/apps/www/_blog/2022-12-10-postgres-crdt.mdx +++ b/apps/www/_blog/2022-12-10-postgres-crdt.mdx @@ -145,3 +145,15 @@ We're likely to discover more (no doubt from a few friendly HN comments). ## Next steps If you want to help with `pg_crdt` the best way is to get involved in the GitHub repo. We have enabled [Discussions](https://github.com/supabase/pg_crdt/discussions) for any and all ideas. If you have experience with CRDTs and you like this approach, don't hesitate to contact one of the team. + +## More Launch Week 6 + +- [Day 1: New Supabase Docs, built with Next.js](https://supabase.com/blog/new-supabase-docs-built-with-nextjs) +- [Day 2: Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn) +- [Day 3: Multi-factor Authentication via Row Level Security Enforcement](https://supabase.com/blog/mfa-auth-via-rls) +- [Day 4: Supabase Wrappers, a Postgres FDW framework written in Rust](https://supabase.com/blog/postgres-foreign-data-wrappers-rust) +- [Day 5: Supabase Vault is now in Beta](https://supabase.com/blog/vault-now-in-beta) +- [Community Day](https://supabase.com/blog/launch-week-6-community-day) +- [Point in Time Recovery is now available](https://supabase.com/blog/postgres-point-in-time-recovery) +- [Custom Domain Names are now available](https://supabase.com/blog/custom-domain-names) +- [Wrap Up: everything we shipped](https://supabase.com/blog/launch-week-6-wrap-up) From 475dad2ae42b4587c81a4e3d1f2adb4be64420fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Wed, 21 Dec 2022 20:14:03 +0000 Subject: [PATCH 125/215] Add LW6 links. --- ...022-12-12-new-supabase-docs-built-with-nextjs.mdx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/www/_blog/2022-12-12-new-supabase-docs-built-with-nextjs.mdx b/apps/www/_blog/2022-12-12-new-supabase-docs-built-with-nextjs.mdx index 41f8ef5ac1e..9f725b9ab84 100644 --- a/apps/www/_blog/2022-12-12-new-supabase-docs-built-with-nextjs.mdx +++ b/apps/www/_blog/2022-12-12-new-supabase-docs-built-with-nextjs.mdx @@ -94,6 +94,12 @@ Docs are more than just the tech behind them. The goal of docs is to give you, t ## More Launch Week 6 -- [Launch Week 6 Hackathon](https://supabase.com/blog/launch-week-6-hackathon) -- [Who We Hire at Supabase](https://supabase.com/blog/who-we-hire) -- [pg_crdt - an experimental CRDT extension for Postgres](https://supabase.com/blog/postgres-crdt) +- [Day 2: Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn) +- [Day 3: Multi-factor Authentication via Row Level Security Enforcement](https://supabase.com/blog/mfa-auth-via-rls) +- [Day 4: Supabase Wrappers, a Postgres FDW framework written in Rust](https://supabase.com/blog/postgres-foreign-data-wrappers-rust) +- [Day 5: Supabase Vault is now in Beta](https://supabase.com/blog/vault-now-in-beta) +- [Community Day](https://supabase.com/blog/launch-week-6-community-day) +- [Point in Time Recovery is now available](https://supabase.com/blog/postgres-point-in-time-recovery) +- [Custom Domain Names are now available](https://supabase.com/blog/custom-domain-names) +- [Wrap Up: everything we shipped](https://supabase.com/blog/launch-week-6-wrap-up) + From 2df59cd798d9fedd4ca4f6d2c5dc8623843f0b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Wed, 21 Dec 2022 20:15:22 +0000 Subject: [PATCH 126/215] Add LW6 links. --- .../2022-12-13-storage-image-resizing-smart-cdn.mdx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/www/_blog/2022-12-13-storage-image-resizing-smart-cdn.mdx b/apps/www/_blog/2022-12-13-storage-image-resizing-smart-cdn.mdx index 00f68ff0f90..2bd558fc1b5 100644 --- a/apps/www/_blog/2022-12-13-storage-image-resizing-smart-cdn.mdx +++ b/apps/www/_blog/2022-12-13-storage-image-resizing-smart-cdn.mdx @@ -205,3 +205,15 @@ A few features to look out for in the future: - Check out the Docs for [Image Resizing](https://supabase.com/docs/guides/storage/image-transformations). - Check out the Docs for the [Storage CDN](https://supabase.com/docs/guides/storage/cdn). - Get Started today with the [JavaScript](https://supabase.com/docs/reference/javascript/storage-from-download) and [Dart](https://supabase.com/docs/reference/dart/storage-from-list) client libraries. + +## More Launch Week 6 + +- [Day 1: New Supabase Docs, built with Next.js](https://supabase.com/blog/new-supabase-docs-built-with-nextjs) +- [Day 2: Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn) +- [Day 3: Multi-factor Authentication via Row Level Security Enforcement](https://supabase.com/blog/mfa-auth-via-rls) +- [Day 4: Supabase Wrappers, a Postgres FDW framework written in Rust](https://supabase.com/blog/postgres-foreign-data-wrappers-rust) +- [Day 5: Supabase Vault is now in Beta](https://supabase.com/blog/vault-now-in-beta) +- [Community Day](https://supabase.com/blog/launch-week-6-community-day) +- [Point in Time Recovery is now available](https://supabase.com/blog/postgres-point-in-time-recovery) +- [Custom Domain Names are now available](https://supabase.com/blog/custom-domain-names) +- [Wrap Up: everything we shipped](https://supabase.com/blog/launch-week-6-wrap-up) From c809bfbf148a41a15fdd26523beddf4df558f39c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Wed, 21 Dec 2022 20:15:44 +0000 Subject: [PATCH 127/215] REmoved a link. --- apps/www/_blog/2022-12-13-storage-image-resizing-smart-cdn.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/www/_blog/2022-12-13-storage-image-resizing-smart-cdn.mdx b/apps/www/_blog/2022-12-13-storage-image-resizing-smart-cdn.mdx index 2bd558fc1b5..deb677996d5 100644 --- a/apps/www/_blog/2022-12-13-storage-image-resizing-smart-cdn.mdx +++ b/apps/www/_blog/2022-12-13-storage-image-resizing-smart-cdn.mdx @@ -209,7 +209,6 @@ A few features to look out for in the future: ## More Launch Week 6 - [Day 1: New Supabase Docs, built with Next.js](https://supabase.com/blog/new-supabase-docs-built-with-nextjs) -- [Day 2: Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn) - [Day 3: Multi-factor Authentication via Row Level Security Enforcement](https://supabase.com/blog/mfa-auth-via-rls) - [Day 4: Supabase Wrappers, a Postgres FDW framework written in Rust](https://supabase.com/blog/postgres-foreign-data-wrappers-rust) - [Day 5: Supabase Vault is now in Beta](https://supabase.com/blog/vault-now-in-beta) From b00e107228f7412f99f2be7b806b7f11ea6955cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Wed, 21 Dec 2022 20:17:30 +0000 Subject: [PATCH 128/215] Add LW6 links. --- apps/www/_blog/2022-12-14-mfa-auth-via-rls.mdx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/www/_blog/2022-12-14-mfa-auth-via-rls.mdx b/apps/www/_blog/2022-12-14-mfa-auth-via-rls.mdx index b2b4cbedff0..bfb8e02b25d 100644 --- a/apps/www/_blog/2022-12-14-mfa-auth-via-rls.mdx +++ b/apps/www/_blog/2022-12-14-mfa-auth-via-rls.mdx @@ -128,10 +128,14 @@ For starters, we are looking to support WebAuthn and FIDO2 compliant devices suc We are grateful to our early MFA users for the support and feedback provided throughout this period. In particular, we would like to thank [Fabian Beer](https://madebyfabian.com), [Cogram](https://www.cogram.com), and [Happl](https://happl.com) whose detailed feedback helped to shape our implementation. We would also like to specially thank the community behind the [pquerna/otp](https://github.com/pquerna/otp) and [ajstarks/svgo](https://github.com/ajstarks/svgo) libraries - their work is indispensable to this implementation. + ## More Launch Week 6 - [Day 1: New Supabase Docs, built with Next.js](https://supabase.com/blog/new-supabase-docs-built-with-nextjs) - [Day 2: Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn) -- [Launch Week 6 Hackathon](https://supabase.com/blog/launch-week-6-hackathon) -- [Who We Hire at Supabase](https://supabase.com/blog/who-we-hire) -- [pg_crdt - an experimental CRDT extension for Postgres](https://supabase.com/blog/postgres-crdt) +- [Day 4: Supabase Wrappers, a Postgres FDW framework written in Rust](https://supabase.com/blog/postgres-foreign-data-wrappers-rust) +- [Day 5: Supabase Vault is now in Beta](https://supabase.com/blog/vault-now-in-beta) +- [Community Day](https://supabase.com/blog/launch-week-6-community-day) +- [Point in Time Recovery is now available](https://supabase.com/blog/postgres-point-in-time-recovery) +- [Custom Domain Names are now available](https://supabase.com/blog/custom-domain-names) +- [Wrap Up: everything we shipped](https://supabase.com/blog/launch-week-6-wrap-up) From a5f0ada501d14fb412d1d4b968a4fa45a6601b1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Wed, 21 Dec 2022 20:18:03 +0000 Subject: [PATCH 129/215] Add LW6 links. --- .../2022-12-15-postgres-foreign-data-wrappers-rust.mdx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/www/_blog/2022-12-15-postgres-foreign-data-wrappers-rust.mdx b/apps/www/_blog/2022-12-15-postgres-foreign-data-wrappers-rust.mdx index c746fd61ba4..62147a24dd0 100644 --- a/apps/www/_blog/2022-12-15-postgres-foreign-data-wrappers-rust.mdx +++ b/apps/www/_blog/2022-12-15-postgres-foreign-data-wrappers-rust.mdx @@ -259,6 +259,8 @@ If you're interested in getting involved or building your own Wrapper, don't hes - [Day 1: New Supabase Docs, built with Next.js](https://supabase.com/blog/new-supabase-docs-built-with-nextjs) - [Day 2: Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn) - [Day 3: Multi-factor Authentication via Row Level Security Enforcement](https://supabase.com/blog/mfa-auth-via-rls) -- [Launch Week 6 Hackathon](https://supabase.com/blog/launch-week-6-hackathon) -- [Who We Hire at Supabase](https://supabase.com/blog/who-we-hire) -- [pg_crdt - an experimental CRDT extension for Postgres](https://supabase.com/blog/postgres-crdt) +- [Day 5: Supabase Vault is now in Beta](https://supabase.com/blog/vault-now-in-beta) +- [Community Day](https://supabase.com/blog/launch-week-6-community-day) +- [Point in Time Recovery is now available](https://supabase.com/blog/postgres-point-in-time-recovery) +- [Custom Domain Names are now available](https://supabase.com/blog/custom-domain-names) +- [Wrap Up: everything we shipped](https://supabase.com/blog/launch-week-6-wrap-up) From 81a4fd2b487964c0beb1829915d3eba76a5c00df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Wed, 21 Dec 2022 20:18:41 +0000 Subject: [PATCH 130/215] Add LW6 links. --- apps/www/_blog/2022-12-16-custom-domain-names.mdx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/www/_blog/2022-12-16-custom-domain-names.mdx b/apps/www/_blog/2022-12-16-custom-domain-names.mdx index 014255f71a8..79ca54ad805 100644 --- a/apps/www/_blog/2022-12-16-custom-domain-names.mdx +++ b/apps/www/_blog/2022-12-16-custom-domain-names.mdx @@ -62,6 +62,8 @@ We're working hard to fix these limitations, so stay tuned for future updates. - [Day 1: New Supabase Docs, built with Next.js](https://supabase.com/blog/new-supabase-docs-built-with-nextjs) - [Day 2: Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn) - [Day 3: Multi-factor Authentication via Row Level Security Enforcement](https://supabase.com/blog/mfa-auth-via-rls) -- [Launch Week 6 Hackathon](https://supabase.com/blog/launch-week-6-hackathon) -- [Who We Hire at Supabase](https://supabase.com/blog/who-we-hire) -- [pg_crdt - an experimental CRDT extension for Postgres](https://supabase.com/blog/postgres-crdt) +- [Day 4: Supabase Wrappers, a Postgres FDW framework written in Rust](https://supabase.com/blog/postgres-foreign-data-wrappers-rust) +- [Day 5: Supabase Vault is now in Beta](https://supabase.com/blog/vault-now-in-beta) +- [Point in Time Recovery is now available](https://supabase.com/blog/postgres-point-in-time-recovery) +- [Custom Domain Names are now available](https://supabase.com/blog/custom-domain-names) +- [Wrap Up: everything we shipped](https://supabase.com/blog/launch-week-6-wrap-up) From 784a11d2b057e2ab8775b6e8819edb73624e50e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Wed, 21 Dec 2022 20:19:11 +0000 Subject: [PATCH 131/215] Add LW6 links. --- apps/www/_blog/2022-12-16-launch-week-6-community-day.mdx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/www/_blog/2022-12-16-launch-week-6-community-day.mdx b/apps/www/_blog/2022-12-16-launch-week-6-community-day.mdx index b0c7f395dc8..81cbbfdf6d7 100644 --- a/apps/www/_blog/2022-12-16-launch-week-6-community-day.mdx +++ b/apps/www/_blog/2022-12-16-launch-week-6-community-day.mdx @@ -237,4 +237,7 @@ We plan to continue our open and collaborative trend. There are many companies w - [Day 2: Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn) - [Day 3: Multi-factor Authentication via Row Level Security Enforcement](https://supabase.com/blog/mfa-auth-via-rls) - [Day 4: Supabase Wrappers, a Postgres FDW framework written in Rust](https://supabase.com/blog/postgres-foreign-data-wrappers-rust) -- [Who We Hire at Supabase](https://supabase.com/blog/who-we-hire) +- [Day 5: Supabase Vault is now in Beta](https://supabase.com/blog/vault-now-in-beta) +- [Point in Time Recovery is now available](https://supabase.com/blog/postgres-point-in-time-recovery) +- [Custom Domain Names are now available](https://supabase.com/blog/custom-domain-names) +- [Wrap Up: everything we shipped](https://supabase.com/blog/launch-week-6-wrap-up) From fc89afeb9213b86c9ba75a3211e94d1f1ff0111a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Wed, 21 Dec 2022 20:19:40 +0000 Subject: [PATCH 132/215] Add LW6 links. --- apps/www/_blog/2022-12-16-new-in-postgres-15.mdx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/www/_blog/2022-12-16-new-in-postgres-15.mdx b/apps/www/_blog/2022-12-16-new-in-postgres-15.mdx index d1b158b5a47..6eb78cee729 100644 --- a/apps/www/_blog/2022-12-16-new-in-postgres-15.mdx +++ b/apps/www/_blog/2022-12-16-new-in-postgres-15.mdx @@ -68,3 +68,15 @@ Before choosing to upgrade, don't forget to check the list of rare cases that ne - If you're self-hosting, upgrading between major PostgreSQL is easily done with [pg_upgrade](https://www.postgresql.org/docs/current/pgupgrade.html) utility or a set of recommended practices in the [official docs](https://www.postgresql.org/docs/current/upgrading.html). - If you're starting a new project on Supabase, you'll already be on PG15. - If you want to upgrade an existing project, check out our [Upgrading](https://supabase.com/docs/guides/platform/migrating-and-upgrading-projects#upgrade-your-project) documentation. + +## More Launch Week 6 + +- [Day 1: New Supabase Docs, built with Next.js](https://supabase.com/blog/new-supabase-docs-built-with-nextjs) +- [Day 2: Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn) +- [Day 3: Multi-factor Authentication via Row Level Security Enforcement](https://supabase.com/blog/mfa-auth-via-rls) +- [Day 4: Supabase Wrappers, a Postgres FDW framework written in Rust](https://supabase.com/blog/postgres-foreign-data-wrappers-rust) +- [Day 5: Supabase Vault is now in Beta](https://supabase.com/blog/vault-now-in-beta) +- [Community Day](https://supabase.com/blog/launch-week-6-community-day) +- [Point in Time Recovery is now available](https://supabase.com/blog/postgres-point-in-time-recovery) +- [Custom Domain Names are now available](https://supabase.com/blog/custom-domain-names) +- [Wrap Up: everything we shipped](https://supabase.com/blog/launch-week-6-wrap-up) From d77d306f2fa82804da56c89c87790af3b1febf0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Wed, 21 Dec 2022 20:20:29 +0000 Subject: [PATCH 133/215] Add LW6 links. --- apps/www/_blog/2022-12-16-pg-graphql-v1.mdx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/www/_blog/2022-12-16-pg-graphql-v1.mdx b/apps/www/_blog/2022-12-16-pg-graphql-v1.mdx index 30c6c59d777..d72dcdfd1d4 100644 --- a/apps/www/_blog/2022-12-16-pg-graphql-v1.mdx +++ b/apps/www/_blog/2022-12-16-pg-graphql-v1.mdx @@ -98,6 +98,9 @@ Longer term we look forward to experimenting with more ambitions features like a - [Day 1: New Supabase Docs, built with Next.js](https://supabase.com/blog/new-supabase-docs-built-with-nextjs) - [Day 2: Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn) - [Day 3: Multi-factor Authentication via Row Level Security Enforcement](https://supabase.com/blog/mfa-auth-via-rls) -- [Launch Week 6 Hackathon](https://supabase.com/blog/launch-week-6-hackathon) -- [Who We Hire at Supabase](https://supabase.com/blog/who-we-hire) -- [pg_crdt - an experimental CRDT extension for Postgres](https://supabase.com/blog/postgres-crdt) +- [Day 4: Supabase Wrappers, a Postgres FDW framework written in Rust](https://supabase.com/blog/postgres-foreign-data-wrappers-rust) +- [Day 5: Supabase Vault is now in Beta](https://supabase.com/blog/vault-now-in-beta) +- [Community Day](https://supabase.com/blog/launch-week-6-community-day) +- [Point in Time Recovery is now available](https://supabase.com/blog/postgres-point-in-time-recovery) +- [Custom Domain Names are now available](https://supabase.com/blog/custom-domain-names) +- [Wrap Up: everything we shipped](https://supabase.com/blog/launch-week-6-wrap-up) From 57bdacc0939f4ef305de7ef67797841dcb0c6702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Wed, 21 Dec 2022 20:20:54 +0000 Subject: [PATCH 134/215] Add LW6 links. --- .../_blog/2022-12-16-postgres-point-in-time-recovery.mdx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/www/_blog/2022-12-16-postgres-point-in-time-recovery.mdx b/apps/www/_blog/2022-12-16-postgres-point-in-time-recovery.mdx index 243adfdb0e1..3e9007dbe0b 100644 --- a/apps/www/_blog/2022-12-16-postgres-point-in-time-recovery.mdx +++ b/apps/www/_blog/2022-12-16-postgres-point-in-time-recovery.mdx @@ -45,6 +45,8 @@ Enabling Point in Time Recovery could be done [in the Dashboard](https://app.sup - [Day 1: New Supabase Docs, built with Next.js](https://supabase.com/blog/new-supabase-docs-built-with-nextjs) - [Day 2: Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn) - [Day 3: Multi-factor Authentication via Row Level Security Enforcement](https://supabase.com/blog/mfa-auth-via-rls) -- [Launch Week 6 Hackathon](https://supabase.com/blog/launch-week-6-hackathon) -- [Who We Hire at Supabase](https://supabase.com/blog/who-we-hire) -- [pg_crdt - an experimental CRDT extension for Postgres](https://supabase.com/blog/postgres-crdt) +- [Day 4: Supabase Wrappers, a Postgres FDW framework written in Rust](https://supabase.com/blog/postgres-foreign-data-wrappers-rust) +- [Day 5: Supabase Vault is now in Beta](https://supabase.com/blog/vault-now-in-beta) +- [Community Day](https://supabase.com/blog/launch-week-6-community-day) +- [Custom Domain Names are now available](https://supabase.com/blog/custom-domain-names) +- [Wrap Up: everything we shipped](https://supabase.com/blog/launch-week-6-wrap-up) From b33df9021828b2ed7ac65c74dd67d3b5ed1f7ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Wed, 21 Dec 2022 20:21:22 +0000 Subject: [PATCH 135/215] Add LW6 links. --- .../www/_blog/2022-12-16-postgrest-11-prerelease.mdx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/www/_blog/2022-12-16-postgrest-11-prerelease.mdx b/apps/www/_blog/2022-12-16-postgrest-11-prerelease.mdx index bbef228a26d..138a576eefc 100644 --- a/apps/www/_blog/2022-12-16-postgrest-11-prerelease.mdx +++ b/apps/www/_blog/2022-12-16-postgrest-11-prerelease.mdx @@ -224,3 +224,15 @@ $ supabase start ``` Please try it and report any bugs, suggestions or ideas! + +## More Launch Week 6 + +- [Day 1: New Supabase Docs, built with Next.js](https://supabase.com/blog/new-supabase-docs-built-with-nextjs) +- [Day 2: Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn) +- [Day 3: Multi-factor Authentication via Row Level Security Enforcement](https://supabase.com/blog/mfa-auth-via-rls) +- [Day 4: Supabase Wrappers, a Postgres FDW framework written in Rust](https://supabase.com/blog/postgres-foreign-data-wrappers-rust) +- [Day 5: Supabase Vault is now in Beta](https://supabase.com/blog/vault-now-in-beta) +- [Community Day](https://supabase.com/blog/launch-week-6-community-day) +- [Point in Time Recovery is now available](https://supabase.com/blog/postgres-point-in-time-recovery) +- [Custom Domain Names are now available](https://supabase.com/blog/custom-domain-names) +- [Wrap Up: everything we shipped](https://supabase.com/blog/launch-week-6-wrap-up) From 00bf19b5dd19d570a2c09168c3a7678245f070a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Wed, 21 Dec 2022 20:21:58 +0000 Subject: [PATCH 136/215] Add LW6 links. --- apps/www/_blog/2022-12-16-vault-now-in-beta.mdx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/www/_blog/2022-12-16-vault-now-in-beta.mdx b/apps/www/_blog/2022-12-16-vault-now-in-beta.mdx index 3d1418be10d..141e75ed838 100644 --- a/apps/www/_blog/2022-12-16-vault-now-in-beta.mdx +++ b/apps/www/_blog/2022-12-16-vault-now-in-beta.mdx @@ -91,6 +91,8 @@ You'll see the Vault appear in the Dashboard over the next month. If you want to - [Day 1: New Supabase Docs, built with Next.js](https://supabase.com/blog/new-supabase-docs-built-with-nextjs) - [Day 2: Supabase Storage v2: Image resizing and Smart CDN](https://supabase.com/blog/storage-image-resizing-smart-cdn) - [Day 3: Multi-factor Authentication via Row Level Security Enforcement](https://supabase.com/blog/mfa-auth-via-rls) -- [Launch Week 6 Hackathon](https://supabase.com/blog/launch-week-6-hackathon) -- [Who We Hire at Supabase](https://supabase.com/blog/who-we-hire) -- [pg_crdt - an experimental CRDT extension for Postgres](https://supabase.com/blog/postgres-crdt) +- [Day 4: Supabase Wrappers, a Postgres FDW framework written in Rust](https://supabase.com/blog/postgres-foreign-data-wrappers-rust) +- [Community Day](https://supabase.com/blog/launch-week-6-community-day) +- [Point in Time Recovery is now available](https://supabase.com/blog/postgres-point-in-time-recovery) +- [Custom Domain Names are now available](https://supabase.com/blog/custom-domain-names) +- [Wrap Up: everything we shipped](https://supabase.com/blog/launch-week-6-wrap-up) From 82d7eaed40952777d258cb4636aadcf6743f2db1 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 21 Dec 2022 17:08:06 -0330 Subject: [PATCH 137/215] More cleanup and key passing --- .../NavigationMenuGuideList.tsx | 19 ++++- .../NavigationMenu/NavigationMenuRefList.tsx | 79 +++++++------------ .../reference/RefFunctionSection.tsx | 21 ++--- 3 files changed, 55 insertions(+), 64 deletions(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx index d5f97f50e6c..32bacbe94b8 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx @@ -191,7 +191,15 @@ const NavigationMenuGuideList: React.FC = ({ id, setMenuLevelId, context : 'hover:text-brand-900 text-scale-1000', ].join(' ')} > - {x.icon && } + {x.icon && ( + {x.icon} + )} + {x.name} @@ -215,7 +223,14 @@ const NavigationMenuGuideList: React.FC = ({ id, setMenuLevelId, context
  • - {x.icon && } + {x.icon && ( + {x.icon} + )} {x.name} diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefList.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefList.tsx index e2db35f487a..377973897a7 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefList.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefList.tsx @@ -8,11 +8,11 @@ import { find } from 'lodash' import Image from 'next/image' import { useTheme } from 'common/Providers' -// import apiCommonSections from '~/../../spec/common-client-libs-sections.json' - import RevVersionDropdown from '~/components/RefVersionDropdown' import { useMenuActiveRefId, useMenuLevelId } from '~/hooks/useMenuState' import { RefIdOptions, RefKeyOptions } from './NavigationMenu' +import { isFuncNotInLibraryOrVersion } from './NavigationMenu.utils' +import { Fragment } from 'react' const FunctionLink = ({ title, @@ -140,9 +140,6 @@ const NavigationMenuRefList: React.FC = ({ }).items.map((x) => x.id) : [] - // console.log(filterIds) - // console.log(modifierIds) - const level = useMenuLevelId() return ( @@ -192,24 +189,8 @@ const NavigationMenuRefList: React.FC = ({
      {sections.map((fn: any, fnIndex) => { - // - // check if the link is allowed to be displayed - function isFuncNotInLibraryOrVersion(id, type) { - if (id && allowedKeys && !allowedKeys.includes(id) && type !== 'markdown') { - /* - * Remove this menu link from side bar, as it does not exist for either - * this language, or for this lib version - * - * */ - return true - } else { - return false - } - } - - // run allow check - if (isFuncNotInLibraryOrVersion(fn.id, fn.type)) { - return <> + if (isFuncNotInLibraryOrVersion(fn.id, fn.type, allowedKeys)) { + return } const RenderLink = (props) => { @@ -261,7 +242,7 @@ const NavigationMenuRefList: React.FC = ({ props.items .filter((item) => allowedKeys.includes(item.id)) .map((item) => { - return + return })} @@ -269,34 +250,28 @@ const NavigationMenuRefList: React.FC = ({ ) } - // handle subtitles with subitems - if (!fn.id) { - return ( - <> - - - {fn.items && - fn.items - //.filter((item) => item.libs && item.libs.includes(lib)) - .map((item) => { - // run allow check - if (isFuncNotInLibraryOrVersion(item.id, item.type)) return <> - return - })} - - ) - } else { - // handle normal links - return ( - <> - - {fn.items && - fn.items - //.filter((item) => item.libs.includes(lib)) - .map((item) => )} - - ) - } + return fn.id ? ( + + + {fn.items && + fn.items.map((item) => ( + + ))} + + ) : ( + + + + {fn.items && + fn.items.map((item, i) => + isFuncNotInLibraryOrVersion(item.id, item.type, allowedKeys) ? ( + + ) : ( + + ) + )} + + ) })}
  • diff --git a/apps/docs/components/reference/RefFunctionSection.tsx b/apps/docs/components/reference/RefFunctionSection.tsx index a31c52af992..32002c389bc 100644 --- a/apps/docs/components/reference/RefFunctionSection.tsx +++ b/apps/docs/components/reference/RefFunctionSection.tsx @@ -9,6 +9,7 @@ import RefSubLayout from '~/layouts/ref/RefSubLayout' import { extractTsDocNode, generateParameters } from '~/lib/refGenerator/helpers' import RefDetailCollapse from '~/components/reference/RefDetailCollapse' +import { Fragment } from 'react' interface ICommonFunc { id: string @@ -32,11 +33,6 @@ const RefFunctionSection: React.FC = (props) => { // gracefully return nothing if function does not exist if (!item) return <> - if (item && !item['$ref']) { - //console.warn('đźš© issue with $ref in:', item.id) - } - - // console.log(item) const hasTsRef = item['$ref'] || null const tsDefinition = @@ -89,25 +85,29 @@ const RefFunctionSection: React.FC = (props) => { }) const paramItem = overide?.length > 0 ? overide[0] : param - return ( - + {paramItem.subContent && (
    {param.subContent.map((param) => { return ( - <> + {param.subContent && ( {param.subContent.map((param) => { - return + return ( + + ) })} )} - + ) })} @@ -150,6 +150,7 @@ const RefFunctionSection: React.FC = (props) => { return ( From eba225523acfec46be9cafdfdf6d8be8f546a06a Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 21 Dec 2022 17:19:08 -0330 Subject: [PATCH 138/215] More keys --- .../NavigationMenu/NavigationMenu.utils.ts | 6 ++++++ .../components/reference/RefSectionHandler.tsx | 17 +++++++++++++---- apps/docs/layouts/ref/RefSubLayout.tsx | 6 ++---- 3 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 apps/docs/components/Navigation/NavigationMenu/NavigationMenu.utils.ts diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.utils.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.utils.ts new file mode 100644 index 00000000000..ad3d100450f --- /dev/null +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.utils.ts @@ -0,0 +1,6 @@ +// check if the link is allowed to be displayed +export function isFuncNotInLibraryOrVersion(id, type, allowedKeys) { + if (id && allowedKeys && !allowedKeys.includes(id) && type !== 'markdown') { + return true + } +} diff --git a/apps/docs/components/reference/RefSectionHandler.tsx b/apps/docs/components/reference/RefSectionHandler.tsx index 75476287461..d54e5d616bb 100644 --- a/apps/docs/components/reference/RefSectionHandler.tsx +++ b/apps/docs/components/reference/RefSectionHandler.tsx @@ -36,16 +36,17 @@ const RefSectionHandler = (props) => { return ( - {props.sections.map((x) => { + {props.sections.map((x, i) => { switch (x.type) { case 'markdown': const markdownData = props.pageProps.docs.find((doc) => doc.id === x.id) - return + return break case 'function': return ( { /> ) case 'cli-command': - return + return break case 'operation': - return + return ( + + ) default: return ( & RefSubLayoutSubComponents = (props) = } const Section: FC = (props) => { - // console.log({ props }) return (
    = (props) => { } const EducationSection: FC = ({ icon, hideTitle = false, ...props }) => { - // console.log({ props }) return (
    {!hideTitle && } From 1c60f4077c2df5cc67dd62467ef0f5253506de70 Mon Sep 17 00:00:00 2001 From: dannykng Date: Wed, 21 Dec 2022 14:04:59 -0800 Subject: [PATCH 139/215] Update Dashboard Auth links --- apps/docs/pages/guides/auth/overview.mdx | 2 +- .../2022-08-03-supabase-beta-update-july-2022.mdx | 2 +- spec/examples/examples.yml | 12 ++++++------ spec/supabase_dart_v1.yml | 4 ++-- spec/supabase_js_v1.yml | 2 +- spec/supabase_js_v2.yml | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/docs/pages/guides/auth/overview.mdx b/apps/docs/pages/guides/auth/overview.mdx index 85b1fdc96da..97044096e13 100644 --- a/apps/docs/pages/guides/auth/overview.mdx +++ b/apps/docs/pages/guides/auth/overview.mdx @@ -55,7 +55,7 @@ You can enable third-party providers with the click of a button by navigating to ### Redirect URLs and wildcards -When using third-party providers, the [Supabase client library](/docs/reference/javascript/auth-signinwithoauth#sign-in-using-a-third-party-provider-with-redirect) redirects the user to the provider. When the third-party provider successfully authenticates the user, the provider redirects the user to the Supabase Auth callback URL where they are further redirected to the URL specified in the `redirectTo` parameter. This parameter defaults to the [`SITE_URL`](/docs/reference/auth/config#site_url). You can modify the `SITE_URL` or [add additional redirect URLs](https://app.supabase.com/project/_/auth/settings). +When using third-party providers, the [Supabase client library](/docs/reference/javascript/auth-signinwithoauth#sign-in-using-a-third-party-provider-with-redirect) redirects the user to the provider. When the third-party provider successfully authenticates the user, the provider redirects the user to the Supabase Auth callback URL where they are further redirected to the URL specified in the `redirectTo` parameter. This parameter defaults to the [`SITE_URL`](/docs/reference/auth/config#site_url). You can modify the `SITE_URL` or add additional [redirect URLs](https://app.supabase.com/project/_/auth/url-configuration). You can use wildcard match patterns to support preview URLs from providers like Netlify and Vercel. See the [full list of supported patterns](https://pkg.go.dev/github.com/gobwas/glob#Compile). Use [this tool](https://www.digitalocean.com/community/tools/glob?comments=true&glob=http%3A%2F%2Flocalhost%3A3000%2F%2A%2A&matches=false&tests=http%3A%2F%2Flocalhost%3A3000&tests=http%3A%2F%2Flocalhost%3A3000%2F&tests=http%3A%2F%2Flocalhost%3A3000%2F%3Ftest%3Dtest&tests=http%3A%2F%2Flocalhost%3A3000%2Ftest-test%3Ftest%3Dtest&tests=http%3A%2F%2Flocalhost%3A3000%2Ftest%2Ftest%3Ftest%3Dtest) to test your patterns. diff --git a/apps/www/_blog/2022-08-03-supabase-beta-update-july-2022.mdx b/apps/www/_blog/2022-08-03-supabase-beta-update-july-2022.mdx index 2330e5f8703..5f7812d4c91 100644 --- a/apps/www/_blog/2022-08-03-supabase-beta-update-july-2022.mdx +++ b/apps/www/_blog/2022-08-03-supabase-beta-update-july-2022.mdx @@ -48,7 +48,7 @@ The new hCpatcha integration allows you to implement captcha functionality for a ![supabase-updated-settings-ui-dashboard](/images/blog/2022-july/auth-config.jpeg) -We've started updating our settings UI, starting with the Auth settings. You'll now see updated UI for setting up your Auth providers, Email templates and SMTP config. [Auth settings](https://app.supabase.com/project/_/auth/settings) +We've started updating our settings UI, starting with the Auth settings. You'll now see updated UI for setting up your [Auth providers](https://app.supabase.com/project/_/auth/providers), [Email templates](https://app.supabase.com/project/_/auth/templates), and [SMTP config](https://app.supabase.com/project/_/auth/url-configuration). ## Quick product announcements diff --git a/spec/examples/examples.yml b/spec/examples/examples.yml index d2bc4403b6e..393fc81e279 100644 --- a/spec/examples/examples.yml +++ b/spec/examples/examples.yml @@ -114,13 +114,13 @@ functions: title: 'signUp()' $ref: '@supabase/gotrue-js.GoTrueClient.signUp' notes: | - - By default, the user needs to verify their email address before logging in. To turn this off, disable **Confirm email** in [your project](https://app.supabase.com/project/_/auth/settings). + - By default, the user needs to verify their email address before logging in. To turn this off, disable **Confirm email** in [your project](https://app.supabase.com/project/_/auth/providers). - **Confirm email** determines if users need to confirm their email address after signing up. - If **Confirm email** is enabled, a `user` is returned but `session` is null. - If **Confirm email** is disabled, both a `user` and a `session` are returned. - - When the user confirms their email address, they are redirected to the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url) by default. You can modify your `SITE_URL` or add additional redirect URLs in [your project](https://app.supabase.com/project/_/auth/settings). + - By default, when the user confirms their email address, they are redirected to the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url). You can modify your `SITE_URL` or add additional redirect URLs in [your project](https://app.supabase.com/project/_/auth/url-configuration). - If signUp() is called for an existing confirmed user: - - If **Confirm email** is enabled in [your project](https://app.supabase.com/project/_/auth/settings), an obfuscated/fake user object is returned. + - If **Confirm email** is enabled in [your project](https://app.supabase.com/project/_/auth/providers), an obfuscated/fake user object is returned. - If **Confirm email** is disabled, the error message, `User already registered` is returned. - To fetch the currently logged-in user, refer to [`getUser()`](/docs/reference/javascript/auth-getuser). examples: @@ -192,7 +192,7 @@ functions: - This method is used for passwordless sign-ins where a OTP is sent to the user's email or phone number. - If you're using an email, you can configure whether you want the user to receive a magiclink or a OTP. - If you're using phone, you can configure whether you want the user to receive a OTP. - - The magic link's destination URL is determined by the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url). You can modify the `SITE_URL` or add additional redirect urls in [your project](https://app.supabase.com/project/_/auth/settings). + - The magic link's destination URL is determined by the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url). You can modify the `SITE_URL` or add additional redirect URLs in [your project](https://app.supabase.com/project/_/auth/url-configuration). examples: - id: sign-in-with-email. name: Sign in with email. @@ -236,7 +236,7 @@ functions: isSpotlight: false description: | When the third-party provider successfully authenticates the user, the provider will redirect the user to the URL specified in the `redirectTo` parameter. This parameter defaults to the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url). It does not redirect the user immediately after invoking this method. - You can modify the `SITE_URL` or add additional redirect urls in [your project](https://app.supabase.com/project/_/auth/settings). + You can modify the `SITE_URL` or add additional redirect URLs in [your project](https://app.supabase.com/project/_/auth/url-configuration). js: | ```js const { data, error } = await supabase.auth.signInWithOAuth({ @@ -334,7 +334,7 @@ functions: notes: | - In order to use the `updateUser()` method, the user needs to be signed in first. - By Default, email updates sends a confirmation link to both the user's current and new email. - To only send a confirmation link to the user's new email, disable **Secure email change** in your project's [email auth provider settings](https://app.supabase.com/project/_/auth/settings). + To only send a confirmation link to the user's new email, disable **Secure email change** in your project's [email auth provider settings](https://app.supabase.com/project/_/auth/providers). examples: - id: update-the-email-for-an-authenticated-user name: Update the email for an authenticated user diff --git a/spec/supabase_dart_v1.yml b/spec/supabase_dart_v1.yml index 304d6988077..9c61cc1ee20 100644 --- a/spec/supabase_dart_v1.yml +++ b/spec/supabase_dart_v1.yml @@ -85,7 +85,7 @@ functions: - This method is used for passwordless sign-ins where a OTP is sent to the user's email or phone number. - If you're using an email, you can configure whether you want the user to receive a magiclink or a OTP. - If you're using phone, you can configure whether you want the user to receive a OTP. - - The magic link's destination URL is determined by the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url). You can modify the `SITE_URL` or add additional redirect urls in [your project](https://app.supabase.com/project/_/auth/settings). + - The magic link's destination URL is determined by the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url). You can modify the `SITE_URL` or add additional redirect urls in [your project](https://app.supabase.com/project/_/auth/url-configuration). examples: - id: sign-in-with-email name: Sign in with email. @@ -229,7 +229,7 @@ functions: notes: | - In order to use the `updateUser()` method, the user needs to be signed in first. - By Default, email updates sends a confirmation link to both the user's current and new email. - To only send a confirmation link to the user's new email, disable **Secure email change** in your project's [email auth provider settings](https://app.supabase.com/project/_/auth/settings). + To only send a confirmation link to the user's new email, disable **Secure email change** in your project's [email auth provider settings](https://app.supabase.com/project/_/auth/providers). examples: - id: update-the-email-for-an-authenticated-user name: Update the email for an authenticated user diff --git a/spec/supabase_js_v1.yml b/spec/supabase_js_v1.yml index b56b1390eac..810e46d2036 100644 --- a/spec/supabase_js_v1.yml +++ b/spec/supabase_js_v1.yml @@ -285,7 +285,7 @@ functions: $ref: '@supabase/gotrue-js.GoTrueClient.update' notes: | User email: By Default, email updates sends a confirmation link to both the user's current and new email. - To only send a confirmation link to the user's new email, disable **Secure email change** in your project's [email auth provider settings](https://app.supabase.com/project/_/auth/settings). + To only send a confirmation link to the user's new email, disable **Secure email change** in your project's [email auth provider settings](https://app.supabase.com/project/_/auth/providers). User metadata: It's generally better to store user data in a table within your public schema (i.e., `public.users`). Use the `update()` method if you have data which rarely changes or is specific only to the logged in user. diff --git a/spec/supabase_js_v2.yml b/spec/supabase_js_v2.yml index df9973635a4..7b4af1c9e81 100644 --- a/spec/supabase_js_v2.yml +++ b/spec/supabase_js_v2.yml @@ -360,7 +360,7 @@ functions: notes: | - In order to use the `updateUser()` method, the user needs to be signed in first. - By Default, email updates sends a confirmation link to both the user's current and new email. - To only send a confirmation link to the user's new email, disable **Secure email change** in your project's [email auth provider settings](https://app.supabase.com/project/_/auth/settings). + To only send a confirmation link to the user's new email, disable **Secure email change** in your project's [email auth provider settings](https://app.supabase.com/project/_/auth/providers). examples: - id: update-the-email-for-an-authenticated-user name: Update the email for an authenticated user From 560b8009b6dbedbe24870d42be3df4fba6e0626e Mon Sep 17 00:00:00 2001 From: dannykng Date: Wed, 21 Dec 2022 14:06:21 -0800 Subject: [PATCH 140/215] Delete old spec files --- spec/cli_v1_commands--old.yaml | 882 ------ spec/supabase_dart_v0--old.yml | 2183 ------------- spec/supabase_dart_v1--old.yml | 2280 -------------- spec/supabase_js_v1--old.yml | 2533 --------------- spec/supabase_js_v2--old.yml | 5390 -------------------------------- 5 files changed, 13268 deletions(-) delete mode 100644 spec/cli_v1_commands--old.yaml delete mode 100644 spec/supabase_dart_v0--old.yml delete mode 100644 spec/supabase_dart_v1--old.yml delete mode 100644 spec/supabase_js_v1--old.yml delete mode 100644 spec/supabase_js_v2--old.yml diff --git a/spec/cli_v1_commands--old.yaml b/spec/cli_v1_commands--old.yaml deleted file mode 100644 index d7af310d127..00000000000 --- a/spec/cli_v1_commands--old.yaml +++ /dev/null @@ -1,882 +0,0 @@ -clispec: '001' -info: - id: cli - version: 1.11.3 - title: Supabase CLI - language: sh - source: https://github.com/supabase/cli - bugs: https://github.com/supabase/cli/issues - spec: https://github.com/supabase/supabase.tools/cli_spec/lib.yaml - description: | - Supabase CLI provides you with tools to develop your application locally, and deploy your application to the Supabase platform. - options: |- - ```sh - --debug output debug logs to stderr - --experimental enable experimental features - -h, --help help for supabase - --workdir string path to a Supabase project directory - ``` - tags: - - id: local-dev - title: Local Development - - id: management-api - title: Management APIs - - id: other-commands - title: Additional Commands -commands: - - id: supabase-test - title: supabase test - summary: Run tests on local Supabase containers - tags: - - local-dev - links: [] - subcommands: - - supabase-test-db - options: |- - ```sh - -h, --help help for test - ``` - - id: supabase-test-db - title: supabase test db - summary: Tests local database with pgTAP - tags: [] - links: [] - usage: |- - ```sh - supabase test db [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for db - ``` - - id: supabase-stop - title: supabase stop - summary: Stop all local Supabase containers - tags: - - local-dev - links: [] - usage: |- - ```sh - supabase stop [flags] - ``` - subcommands: [] - options: |- - ```sh - --backup Backs up the current database before stopping. - -h, --help help for stop - ``` - - id: supabase-status - title: supabase status - summary: Show status of local Supabase containers - tags: - - local-dev - links: [] - usage: |- - ```sh - supabase status [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for status - ``` - - id: supabase-start - title: supabase start - summary: Start containers for Supabase local development - tags: - - local-dev - links: [] - usage: |- - ```sh - supabase start [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for start - ``` - - id: supabase-secrets - title: supabase secrets - summary: Manage Supabase secrets - tags: - - management-api - links: [] - subcommands: - - supabase-secrets-list - - supabase-secrets-set - - supabase-secrets-unset - options: |- - ```sh - -h, --help help for secrets - ``` - - id: supabase-secrets-unset - title: supabase secrets unset - summary: Unset a secret(s) on Supabase - description: Unset a secret(s) from the linked Supabase project. - tags: [] - links: [] - usage: |- - ```sh - supabase secrets unset ... [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for unset - ``` - - id: supabase-secrets-set - title: supabase secrets set - summary: Set a secret(s) on Supabase - description: Set a secret(s) to the linked Supabase project. - tags: [] - links: [] - usage: |- - ```sh - supabase secrets set [flags] ... - ``` - subcommands: [] - options: |- - ```sh - --env-file string Read secrets from a .env file. - -h, --help help for set - ``` - - id: supabase-secrets-list - title: supabase secrets list - summary: List all secrets on Supabase - description: List all secrets in the linked project. - tags: [] - links: [] - usage: |- - ```sh - supabase secrets list [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for list - ``` - - id: supabase-projects - title: supabase projects - summary: Manage Supabase projects - tags: - - management-api - links: [] - subcommands: - - supabase-projects-create - - supabase-projects-list - options: |- - ```sh - -h, --help help for projects - ``` - - id: supabase-projects-list - title: supabase projects list - summary: List all Supabase projects - description: List all Supabase projects the logged-in user can access. - tags: [] - links: [] - usage: |- - ```sh - supabase projects list [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for list - ``` - - id: supabase-projects-create - title: supabase projects create - summary: Create a project on Supabase - tags: [] - links: [] - usage: |- - ```sh - supabase projects create [flags] - ``` - subcommands: [] - options: |- - ```sh - --db-password string Database password of the project. - -h, --help help for create - -i, --interactive Enables interactive mode. - --org-id string Organization ID to create the project in. - --plan string Select a plan that suits your needs. (default "free") - --region string Select a region close to you for the best performance. - ``` - - id: supabase-orgs - title: supabase orgs - summary: Manage Supabase organizations - tags: - - management-api - links: [] - subcommands: - - supabase-orgs-list - options: |- - ```sh - -h, --help help for orgs - ``` - - id: supabase-orgs-list - title: supabase orgs list - summary: List all organizations - description: List all organizations the logged-in user belongs. - tags: [] - links: [] - usage: |- - ```sh - supabase orgs list [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for list - ``` - - id: supabase-migration - title: supabase migration - summary: Manage database migration scripts - tags: - - local-dev - links: [] - subcommands: - - supabase-migration-list - - supabase-migration-new - options: |- - ```sh - -h, --help help for migration - ``` - - id: supabase-migration-new - title: supabase migration new - summary: Create an empty migration script - tags: [] - links: [] - usage: |- - ```sh - supabase migration new [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for new - ``` - - id: supabase-migration-list - title: supabase migration list - summary: List local and remote migrations - tags: [] - links: [] - usage: |- - ```sh - supabase migration list [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for list - -p, --password string Password to your remote Postgres database. - ``` - - id: supabase-login - title: supabase login - summary: Authenticate using an access token - tags: - - local-dev - links: [] - usage: |- - ```sh - supabase login [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for login - ``` - - id: supabase-link - title: supabase link - summary: Link to a Supabase project - tags: - - local-dev - links: [] - usage: |- - ```sh - supabase link [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for link - -p, --password string Password to your remote Postgres database. - --project-ref string Project ref of the Supabase project. - ``` - - id: supabase-init - title: supabase init - summary: Initialize a local project - tags: - - local-dev - links: [] - usage: |- - ```sh - supabase init [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for init - ``` - - id: supabase-gen - title: supabase gen - summary: Run code generation tools - tags: - - local-dev - links: [] - subcommands: - - supabase-gen-types - options: |- - ```sh - -h, --help help for gen - ``` - - id: supabase-gen-types - title: supabase gen types - summary: Generate types from Postgres schema - tags: [] - links: [] - subcommands: - - supabase-gen-types-typescript - options: |- - ```sh - -h, --help help for types - ``` - - id: supabase-gen-types-typescript - title: supabase gen types typescript - summary: Generate types for TypeScript - description: | - Generate types for TypeScript. Must specify one of --local, --linked, --project-id, or --db-url - tags: [] - links: [] - usage: |- - ```sh - supabase gen types typescript [flags] - ``` - subcommands: [] - options: |- - ```sh - --db-url string Generate types from a database url. - -h, --help help for typescript - --linked Generate types from the linked project. - --local Generate types from the local dev database. - --project-id string Generate types from a project ID. - --schema stringArray Schemas to generate types for. - ``` - - id: supabase-functions - title: supabase functions - summary: Manage Supabase Edge functions - tags: - - management-api - links: [] - subcommands: - - supabase-functions-delete - - supabase-functions-deploy - - supabase-functions-new - - supabase-functions-serve - options: |- - ```sh - -h, --help help for functions - ``` - - id: supabase-functions-serve - title: supabase functions serve - summary: Serve a Function locally - tags: [] - links: [] - usage: |- - ```sh - supabase functions serve [flags] - ``` - subcommands: [] - options: |- - ```sh - --env-file string Path to an env file to be populated to the Function environment. - -h, --help help for serve - --no-verify-jwt Disable JWT verification for the Function. - ``` - - id: supabase-functions-new - title: supabase functions new - summary: Create a new Function locally - tags: [] - links: [] - usage: |- - ```sh - supabase functions new [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for new - ``` - - id: supabase-functions-deploy - title: supabase functions deploy - summary: Deploy a Function to Supabase - description: Deploy a Function to the linked Supabase project. - tags: [] - links: [] - usage: |- - ```sh - supabase functions deploy [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for deploy - --no-verify-jwt Disable JWT verification for the Function. - --project-ref string Project ref of the Supabase project. - ``` - - id: supabase-functions-delete - title: supabase functions delete - summary: Delete a Function from Supabase - description: | - Delete a Function from the linked Supabase project. This does NOT remove the Function locally. - tags: [] - links: [] - usage: |- - ```sh - supabase functions delete [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for delete - --project-ref string Project ref of the Supabase project. - ``` - - id: supabase-domains - title: supabase domains - summary: Manage custom domain names for Supabase projects - tags: - - management-api - links: [] - subcommands: - - supabase-domains-activate - - supabase-domains-create - - supabase-domains-delete - - supabase-domains-get - - supabase-domains-reverify - options: |- - ```sh - -h, --help help for domains - --include-raw-output Include raw output (useful for debugging). - --project-ref string Project ref of the Supabase project. - ``` - - id: supabase-domains-reverify - title: supabase domains reverify - summary: Re-verify the custom hostname config for your project. - tags: [] - links: [] - usage: |- - ```sh - supabase domains reverify [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for reverify - ``` - - id: supabase-domains-get - title: supabase domains get - summary: Get the current custom hostname config. - description: | - Retrieve the custom hostname config for your project, as stored in the Supabase platform. - tags: [] - links: [] - usage: |- - ```sh - supabase domains get [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for get - ``` - - id: supabase-domains-delete - title: supabase domains delete - summary: Deletes the custom hostname config for your project. - tags: [] - links: [] - usage: |- - ```sh - supabase domains delete [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for delete - ``` - - id: supabase-domains-create - title: supabase domains create - summary: Create a custom hostname. - description: |- - Create a custom hostname for your Supabase project. - - Expects your custom hostname to have a CNAME record to your Supabase project's subdomain. - tags: [] - links: [] - usage: |- - ```sh - supabase domains create [flags] - ``` - subcommands: [] - options: |- - ```sh - --custom-hostname string The custom hostname to use for your Supabase project. - -h, --help help for create - ``` - - id: supabase-domains-activate - title: supabase domains activate - summary: Activate the custom hostname for a project. - description: |- - Activates the custom hostname configuration for a project. - - This reconfigures your Supabase project to respond to requests on your custom hostname. - After the custom hostname is activated, your project's auth services will no longer function on the Supabase-provisioned subdomain. - tags: [] - links: [] - usage: |- - ```sh - supabase domains activate [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for activate - ``` - - id: supabase-db - title: supabase db - summary: Manage local Postgres databases - tags: - - local-dev - links: [] - subcommands: - - supabase-db-branch - - supabase-db-diff - - supabase-db-lint - - supabase-db-push - - supabase-db-remote - - supabase-db-reset - options: |- - ```sh - -h, --help help for db - ``` - - id: supabase-db-reset - title: supabase db reset - summary: Resets the local database to current migrations - tags: [] - links: [] - usage: |- - ```sh - supabase db reset [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for reset - ``` - - id: supabase-db-remote - title: supabase db remote - summary: Manage remote databases - tags: [] - links: [] - subcommands: - - supabase-db-remote-changes - - supabase-db-remote-commit - options: |- - ```sh - -h, --help help for remote - -p, --password string Password to your remote Postgres database. - ``` - - id: supabase-db-remote-commit - title: supabase db remote commit - summary: Commit remote changes as a new migration - tags: [] - links: [] - usage: |- - ```sh - supabase db remote commit [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for commit - ``` - - id: supabase-db-remote-changes - title: supabase db remote changes - summary: Show changes on the remote database - description: Show changes on the remote database since last migration. - tags: [] - links: [] - usage: |- - ```sh - supabase db remote changes [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for changes - ``` - - id: supabase-db-push - title: supabase db push - summary: Push new migrations to the remote database - tags: [] - links: [] - usage: |- - ```sh - supabase db push [flags] - ``` - subcommands: [] - options: |- - ```sh - --dry-run Print the migrations that would be applied, but don't actually apply them. - -h, --help help for push - -p, --password string Password to your remote Postgres database. - ``` - - id: supabase-db-lint - title: supabase db lint - summary: Checks local database for typing error - tags: [] - links: [] - usage: |- - ```sh - supabase db lint [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for lint - --level string Error level to emit. (default "warning") - -s, --schema strings List of schema to include. (default [public]) - ``` - - id: supabase-db-diff - title: supabase db diff - summary: Diffs the local database for schema changes - tags: [] - links: [] - usage: |- - ```sh - supabase db diff [flags] - ``` - subcommands: [] - options: |- - ```sh - -f, --file string Saves schema diff to a file. - -h, --help help for diff - --linked Diffs local schema against linked project. - -s, --schema strings List of schema to include. (default [public]) - --use-migra Use migra to generate schema diff. - ``` - - id: supabase-db-branch - title: supabase db branch - summary: Manage local database branches - description: | - Manage local database branches. Each branch is associated with a separate local database. Forking remote databases is NOT supported. - tags: [] - links: [] - subcommands: - - supabase-db-branch-create - - supabase-db-branch-delete - - supabase-db-branch-list - - supabase-db-branch-switch - options: |- - ```sh - -h, --help help for branch - ``` - - id: supabase-db-branch-switch - title: supabase db branch switch - summary: Switch the active branch - tags: [] - links: [] - usage: |- - ```sh - supabase db branch switch [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for switch - ``` - - id: supabase-db-branch-list - title: supabase db branch list - summary: List branches - tags: [] - links: [] - usage: |- - ```sh - supabase db branch list [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for list - ``` - - id: supabase-db-branch-delete - title: supabase db branch delete - summary: Delete a branch - tags: [] - links: [] - usage: |- - ```sh - supabase db branch delete [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for delete - ``` - - id: supabase-db-branch-create - title: supabase db branch create - summary: Create a branch - tags: [] - links: [] - usage: |- - ```sh - supabase db branch create [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for create - ``` - - id: supabase-completion - title: supabase completion - summary: Generate the autocompletion script for the specified shell - description: | - Generate the autocompletion script for supabase for the specified shell. - See each sub-command's help for details on how to use the generated script. - tags: - - other-commands - links: [] - subcommands: - - supabase-completion-bash - - supabase-completion-fish - - supabase-completion-powershell - - supabase-completion-zsh - options: |- - ```sh - -h, --help help for completion - ``` - - id: supabase-completion-zsh - title: supabase completion zsh - summary: Generate the autocompletion script for zsh - description: | - Generate the autocompletion script for the zsh shell. - - If shell completion is not already enabled in your environment you will need - to enable it. You can execute the following once: - - echo "autoload -U compinit; compinit" >> ~/.zshrc - - To load completions in your current shell session: - - source <(supabase completion zsh); compdef _supabase supabase - - To load completions for every new session, execute once: - - #### Linux: - - supabase completion zsh > "${fpath[1]}/_supabase" - - #### macOS: - - supabase completion zsh > $(brew --prefix)/share/zsh/site-functions/_supabase - - You will need to start a new shell for this setup to take effect. - tags: [] - links: [] - usage: |- - ```sh - supabase completion zsh [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for zsh - --no-descriptions disable completion descriptions - ``` - - id: supabase-completion-powershell - title: supabase completion powershell - summary: Generate the autocompletion script for powershell - description: | - Generate the autocompletion script for powershell. - - To load completions in your current shell session: - - supabase completion powershell | Out-String | Invoke-Expression - - To load completions for every new session, add the output of the above command - to your powershell profile. - tags: [] - links: [] - usage: |- - ```sh - supabase completion powershell [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for powershell - --no-descriptions disable completion descriptions - ``` - - id: supabase-completion-fish - title: supabase completion fish - summary: Generate the autocompletion script for fish - description: | - Generate the autocompletion script for the fish shell. - - To load completions in your current shell session: - - supabase completion fish | source - - To load completions for every new session, execute once: - - supabase completion fish > ~/.config/fish/completions/supabase.fish - - You will need to start a new shell for this setup to take effect. - tags: [] - links: [] - usage: |- - ```sh - supabase completion fish [flags] - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for fish - --no-descriptions disable completion descriptions - ``` - - id: supabase-completion-bash - title: supabase completion bash - summary: Generate the autocompletion script for bash - description: | - Generate the autocompletion script for the bash shell. - - This script depends on the 'bash-completion' package. - If it is not installed already, you can install it via your OS's package manager. - - To load completions in your current shell session: - - source <(supabase completion bash) - - To load completions for every new session, execute once: - - #### Linux: - - supabase completion bash > /etc/bash_completion.d/supabase - - #### macOS: - - supabase completion bash > $(brew --prefix)/etc/bash_completion.d/supabase - - You will need to start a new shell for this setup to take effect. - tags: [] - links: [] - usage: |- - ```sh - supabase completion bash - ``` - subcommands: [] - options: |- - ```sh - -h, --help help for bash - --no-descriptions disable completion descriptions - ``` diff --git a/spec/supabase_dart_v0--old.yml b/spec/supabase_dart_v0--old.yml deleted file mode 100644 index 90dab81a758..00000000000 --- a/spec/supabase_dart_v0--old.yml +++ /dev/null @@ -1,2183 +0,0 @@ -openref: 0.1 - -info: - id: reference/dart - title: Getting started - description: | - - Supabase Dart. - - definition: ../../spec/enrichments/tsdoc_v1/combined.json - slugPrefix: '/' - specUrl: https://github.com/supabase/supabase/edit/master/spec/supabase_dart_v0.yml - libraries: - - name: 'Dart' - id: 'dart' - version: '0.0.1' - -pages: - Installing: - description: | - ## Dart - - Dart libraries are built and supported by the community. - - ```bash - dart pub add supabase - ``` - - Find the source code on [GitHub](https://github.com/supabase/supabase-dart). - - ## Flutter - - For Flutter project, you can use [supabase_flutter](https://github.com/supabase/supabase-flutter). - - ```bash - flutter pub add supabase_flutter - ``` - - `supabase_flutter` plugin uses `supabase` plugin internally, and it adds some Flutter specific functionality such as handling deeplinks coming back from magic link verifications. - If you are creating a Flutter application, we recommend using `supabase_flutter` instead of `supabase`. - - For the most part `supabase_flutter` shares the same API as `supabase` with few exceptions such as initialization or OAuth sign in. - - Initializing: - description: | - ## Dart - You can initialize a new Supabase client using the `SupabaseClient()` method. - - The Supabase client is your entrypoint to the rest of the Supabase functionality - and is the easiest way to interact with everything we offer within the Supabase ecosystem. - - - ## Flutter - - For `supabase_flutter`, you will be using the static `initialize()` method on `Supabase` class. - - examples: - - name: Dart SupabaseClient() - dart: | - ```dart - final supabase = SupabaseClient('https://xyzcompany.supabase.co', 'public-anon-key'); - ``` - - name: Flutter initialize() - dart: | - ```dart title="main.dart" - Future main() async { - await Supabase.initialize(url: 'https://xyzcompany.supabase.co', anonKey: 'public-anon-key'); - runApp(MyApp()); - } - ``` - - auth.signUp(): - description: | - Creates a new user. - notes: | - - By default, the user will need to verify their email address before logging in. If you would like to change this, you can disable "Email Confirmations" by going to Authentication -> Settings on [app.supabase.com](https://app.supabase.com) - - If "Email Confirmations" is turned on, a user is returned but session will be null - - If "Email Confirmations" is turned off, both a `user` and a `session` will be returned - - When the user confirms their email address, they will be redirected to localhost:3000 by default. To change this, you can go to Authentication -> Settings on [app.supabase.com](https://app.supabase.com) - examples: - - name: Sign up. - isSpotlight: true - dart: | - ```dart - final res = await supabase.auth.signUp('example@email.com', 'example-password'); - - final user = res.data?.user; - final error = res.error; - ``` - - name: Sign up with third-party providers. - hideCodeBlock: true - description: | - If you are using Flutter, you can sign up with OAuth providers using the [`signInWithProvider()`](/docs/reference/dart/v0/auth-signinwithprovider) method available on `supabase_flutter`. - - auth.signIn(): - description: | - Log in an existing user, or login via a third-party provider. - notes: | - - A user can sign up via email, phone number. - - If you provide `email` without a `password`, the user will be sent a magic link. - - The magic link's destination URL is determined by the SITE_URL config variable. To change this, you can go to Authentication -> Settings on [app.supabase.com](https://app.supabase.com) - - Similarly, if you provide `phone` without a `password`, the user will be sent a one time password. - - If you are looking to sign users in with OAuth in Flutter apps, go to [`signInWithProvider()`](/docs/reference/dart/v0/auth-signinwithprovider). - examples: - - name: Sign in with email. - isSpotlight: true - dart: | - ```dart - final res = await supabase.auth.signIn(email: 'example@email.com', password: 'example-password'); - - final user = res.data?.user; - final error = res.error; - ``` - - name: Sign in with magic link. - description: If email is provided, but no password is provided, the user will be sent a "magic link" to their email address, which they can click to open your application with a valid session. By default, a given user can only request a Magic Link once every 60 seconds. - dart: | - ```dart - final res = await supabase.auth.signIn(email: 'example@email.com'); - - final error = res.error; - ``` - - name: Get OAuth sign in URL. - description: | - Passing provider parameter to `signIn()` will return a URL to sign your user in via OAuth. - If you are looking to sign in a user via OAuth on Flutter app, go to [`signInWithProvider()`](/docs/reference/dart/v0/auth-signinwithprovider). - dart: | - ```dart - final res = await supabase.auth.signIn(provider: Provider.github); - - final url = res.data?.url; - final error = res.error; - ``` - auth.signInWithProvider(): - description: | - Signs the user in using third party OAuth providers. - notes: | - - `auth.signInWithProvider()` is only available on `supabase_flutter` - - It will open the browser to the relevant login page. - examples: - - name: Sign in with provider. - isSpotlight: true - dart: | - ```dart - final res = await supabase.auth.signInWithProvider(Provider.github); - - final error = res.error; - ``` - - name: With `redirectTo` - description: | - Specify the redirect link to bring back the user via deeplink. - Note that `redirectTo` should be null for Flutter Web. - dart: | - ```dart - final res = await supabase.auth.signInWithProvider( - Provider.github, - options: AuthOptions( - redirectTo: kIsWeb - ? null - : 'io.supabase.flutter://reset-callback/'), - ); - - final error = res.error; - ``` - - name: With scopes - description: | - If you need additional data from an OAuth provider, you can include a space-separated list of scopes in your request to get back an OAuth provider token. - You may also need to specify the scopes in the provider's OAuth app settings, depending on the provider. - dart: | - ```dart - const { user, session, error } = await supabase.auth.signIn({ - provider: 'github' - }, { - scopes: 'repo gist notifications' - }) - const oAuthToken = session.provider_token // use to access provider API - ``` - auth.signOut(): - description: | - Signs out the current user, if there is a logged in user. - examples: - - name: Sign out - isSpotlight: true - dart: | - ```dart - final res = await supabase.auth.signOut(); - - final error = res.error; - ``` - - auth.session(): - description: | - Returns the session data, if there is an active session. - examples: - - name: Get the session data - isSpotlight: true - dart: | - ```dart - final session = supabase.auth.session(); - ``` - - auth.user(): - description: | - Returns the user data, if there is a logged in user. - examples: - - name: Get the logged in user - isSpotlight: true - dart: | - ```dart - final user = supabase.auth.user(); - ``` - - auth.update(): - description: | - Updates user data, if there is a logged in user. - notes: | - It's generally better to store user data in a table inside your public schema (i.e. `public.users`). - Use the `update()` method if you have data which rarely changes or is specific only to the logged in user. - examples: - - name: Update a user's metadata. - isSpotlight: true - dart: | - ```dart - final res = await supabase.auth.update( - UserAttributes(data: {'hello': 'world'}) - ); - - final error = res.error; - ``` - - auth.onAuthStateChange(): - description: | - Receive a notification every time an auth event happens. - examples: - - name: Listen to auth changes - isSpotlight: true - dart: | - ```dart - final subscription = supabase.auth.onAuthStateChange((event, session) { - print(session?.user?.id); - // handle auth state change - }); - ``` - - Reset Password (Email): - description: | - Sends a reset request to an email address. - notes: | - Sends a reset request to an email address. - - When the user clicks the reset link in the email they will be forwarded to: - - `#access_token=x&refresh_token=y&expires_in=z&token_type=bearer&type=recovery` - - Your app must detect `type=recovery` in the fragment and display a password reset form to the user. - - You should then use the access_token in the url and new password to update the user as follows: - - ```dart - final res = await supabase.auth.api.updateUser( - accessToken, - UserAttributes(password: 'NEW_PASSWORD'), - ); - ``` - examples: - - name: Reset password - isSpotlight: true - dart: | - ```dart - final res = await supabase.auth.api.resetPasswordForEmail('user@example.com'); - - final error = res.error; - ``` - - name: Reset password for Flutter - isSpotlight: true - dart: | - You can pass `redirectTo` to open the app via deeplink when user opens the password reset email. - ```dart - final res = await supabase.auth.api.resetPasswordForEmail( - 'user@example.com', - options: AuthOptions(redirectTo: kIsWeb - ? null - : 'io.supabase.flutter://reset-callback/'), - ); - - final error = res.error; - ``` - invoke(): - title: 'invoke()' - description: | - Invokes a Supabase Function. See the [guide](/docs/guides/functions) for details on writing Functions. - notes: | - - Requires an Authorization header. - - Invoke params generally match the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) spec. - examples: - - name: Basic invocation. - isSpotlight: true - dart: | - ```dart - final res = await supabaseClient.functions.invoke('hello', body: {'foo': 'baa'}); - final data = res.data; - final error = res.error; - ``` - - name: Specifying response type. - description: | - By default, `invoke()` will parse the response as JSON. You can parse the response in the following formats: `json`, `blob`, `text`, and `arrayBuffer`. - isSpotlight: true - dart: | - ```dart - final res = await supabaseClient.functions.invoke( - 'hello', - body: {'foo': 'baa'}, - responseType: ResponseType.text, - ); - final data = res.data; - final error = res.error; - ``` - - name: Parsing custom headers. - description: | - Any `headers` will be passed through to the function. A common pattern is to pass a logged-in user's JWT token as an Authorization header. - isSpotlight: true - dart: | - ```dart - final res = await supabaseClient.functions.invoke( - 'hello', - body: {'foo': 'baa'}, - headers: { - 'Authorization': 'Bearer ${supabase.auth.session()?.access_token}' - }, - ); - ``` - select(): - description: | - Performs vertical filtering with SELECT. - title: 'Fetch data: select()' - notes: | - - By default, Supabase projects will return a maximum of 1,000 rows. This setting can be changed in Project API Settings. It's recommended that you keep it low to limit the payload size of accidental or malicious requests. You can use `range()` queries to paginate through your data. - - `select()` can be combined with [Modifiers](/docs/reference/dart/using-modifiers) - - `select()` can be combined with [Filters](/docs/reference/dart/using-filters) - - If using the Supabase hosted platform `apikey` is technically a reserved keyword, since the API gateway will pluck it out for authentication. [It should be avoided as a column name](https://github.com/supabase/supabase/issues/5465). - examples: - - name: Getting your data - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select() - .execute(); - - final data = res.data; - final error = res.error; - ``` - - name: Selecting specific columns - description: You can select specific fields from your tables. - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name') - .execute(); - ``` - - name: Query foreign tables - description: If your database has relationships, you can query related tables too. - dart: | - ```dart - final res = await supabase - .from('countries') - .select(''' - name, - cities ( - name - ) - ''') - .execute(); - ``` - - name: Query the same foreign table multiple times - description: | - Sometimes you will need to query the same foreign table twice. - In this case, you can use the name of the joined column to identify - which join you intend to use. For convenience, you can also give an - alias for each column. For example, if we had a shop of products, - and we wanted to get the supplier and the purchaser at the same time - (both in the users) table: - dart: | - ```dart - final res = await supabase - .from('products') - .select(''' - id, - supplier:supplier_id ( name ), - purchaser:purchaser_id ( name ) - ''') - .execute(); - ``` - - name: Filtering with inner joins - description: | - If you want to filter a table based on a child table's values you can use the `!inner()` function. For example, if you wanted - to select all rows in a `message` table which belong to a user with the `username` "Jane": - dart: | - ```dart - final res = await supabase - .from('messages') - .select('*, users!inner(*)') - .eq('users.username', 'Jane') - .execute(); - ``` - - name: Querying with count option - description: | - You can get the number of rows by using the count option. - Allowed values for count option are [exact](https://postgrest.org/en/stable/api.html#exact-count), [planned](https://postgrest.org/en/stable/api.html#planned-count) and [estimated](https://postgrest.org/en/stable/api.html#estimated-count). - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name') - .execute(count: CountOption.exact); - - final count = res.count; - ``` - - name: Querying JSON data - description: | - If you have data inside of a JSONB column, you can apply select - and query filters to the data values. Postgres offers a - [number of operators](https://www.postgresql.org/docs/current/functions-json.html) - for querying JSON data. Also see - [PostgREST docs](http://postgrest.org/en/v7.0.0/api.html#json-columns) for more details. - dart: | - ```dart - final res = await supabase - .from('users') - .select(''' - id, name, - address->street - ''') - .eq('address->postcode', 90210) - .execute(); - ``` - - name: Return data as CSV - description: | - By default the data is returned in JSON format, however you can also request for it to be returned as Comma Separated Values. - dart: | - ```dart - final res = await supabase - .from('users') - .select() - .csv() - .execute(); - ``` - - insert(): - description: | - Performs an INSERT into the table. - title: 'Create data: insert()' - notes: | - - By default, every time you run `insert()`, the client library will make a `select` to return the full record. - This is convenient, but it can also cause problems if your Policies are not configured to allow the `select` operation. - If you are using Row Level Security and you are encountering problems, try setting the `returning` param to `minimal`. - examples: - - name: Create a record - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .insert([ - {'name': 'The Shire', 'country_id': 554} - ]).execute(); - ``` - - name: Bulk create - dart: | - ```dart - final res = await supabase - .from('cities') - .insert([ - {'name': 'The Shire', 'country_id': 554}, - {'name': 'Rohan', 'country_id': 555}, - ]).execute(); - ``` - - update(): - description: | - Performs an UPDATE on the table. - title: 'Modify data: update()' - notes: | - - `update()` should always be combined with [Filters](/docs/reference/dart/using-filters) to target the item(s) you wish to update. - examples: - - name: Updating your data - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .update({ 'name': 'Middle Earth' }) - .match({ 'name': 'Auckland' }) - .execute(); - ``` - - name: Updating JSON data - description: | - Postgres offers a - [number of operators](https://www.postgresql.org/docs/current/functions-json.html) - for working with JSON data. Right now it is only possible to update an entire JSON document, - but we are [working on ideas](https://github.com/PostgREST/postgrest/issues/465) for updating individual keys. - dart: | - ```dart - final res = await supabase - .from('users') - .update({ - 'address': { - 'street': 'Melrose Place', - 'postcode': 90210 - } - }) - .eq('address->postcode', 90210) - .execute(); - ``` - - upsert(): - description: | - Performs an UPSERT into the table. - title: 'Upsert data: upsert()' - notes: | - - Primary keys should be included in the data payload in order for an update to work correctly. - - Primary keys must be natural, not surrogate. There are however, [workarounds](https://github.com/PostgREST/postgrest/issues/1118) for surrogate primary keys. - examples: - - name: Upsert your data - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('messages') - .upsert({ 'id': 3, 'message': 'foo', 'username': 'supabot' }) - .execute(); - ``` - - name: Upserting into tables with constraints - description: | - Running the following will cause supabase to upsert data into the `users` table. - If the username 'supabot' already exists, the `onConflict` argument tells supabase to overwrite that row - based on the column passed into `onConflict`. - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('users') - .upsert({ 'username': 'supabot' }, { 'onConflict': 'username' }) - .execute(); - ``` - - name: Return the exact number of rows - description: | - Allowed values for count option are `exact`, `planned` and `estimated`. - dart: | - ```dart - final res = await supabase - .from('users') - .upsert({ - 'id': 3, - 'message': 'foo', - 'username': 'supabot' - }) - .execute(count: CountOption.exact); - ``` - - delete(): - description: | - Performs a DELETE on the table. - title: 'Delete data: delete()' - notes: | - - `delete()` should always be combined with [Filters](/docs/reference/dart/using-filters) to target the item(s) you wish to delete. - examples: - - name: Delete records - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .delete() - .match({ 'id': 666 }) - .execute(); - ``` - - rpc(): - title: 'Stored Procedures: rpc()' - description: | - You can call stored procedures as a "Remote Procedure Call". - - That's a fancy way of saying that you can put some logic into your database then call it from anywhere. - It's especially useful when the logic rarely changes - like password resets and updates. - examples: - - name: Call a stored procedure - isSpotlight: true - description: This is an example invoking a stored procedure. - dart: | - ```dart - final res = await supabase - .rpc('hello_world') - .execute(); - ``` - - name: With Parameters - dart: | - ```dart - final res = await supabase - .rpc('echo_city', params: { 'name': 'The Shire' }) - .execute(); - ``` - - name: With count option - description: | - You can specify a count option to get the row count along with your data. - Allowed values for count option are `exact`, `planned` and `estimated`. - dart: | - ```dart - final res = await supabase - .rpc('hello_world') - .execute(count: CountOption.exact); - ``` - - subscribe(): - description: | - Subscribe to realtime changes in your database. - title: 'on().subscribe()' - notes: | - - Realtime is disabled by default for new Projects for better database performance and security. You can turn it on by [managing replication](/docs/guides/api#managing-realtime). - - If you want to receive the "previous" data for updates and deletes, you will need to set `REPLICA IDENTITY` to `FULL`, like this: `ALTER TABLE your_table REPLICA IDENTITY FULL;` - examples: - - name: Listen to all database changes - isSpotlight: true - dart: | - ```dart - final mySubscription = supabase - .from('countries') - .on(SupabaseEventTypes.all, (payload) { - // Handle realtime payload - }) - .subscribe(); - ``` - - name: Listening to a specific table - dart: | - ```dart - final mySubscription = supabase - .from('countries') - .on(SupabaseEventTypes.all, (payload) { - // Handle realtime payload - }) - .subscribe(); - ``` - - name: Listening to inserts - dart: | - ```dart - final mySubscription = supabase - .from('countries') - .on(SupabaseEventTypes.insert, (payload) { - // Handle realtime payload - }) - .subscribe(); - ``` - - name: Listening to updates - description: | - By default, Supabase will send only the updated record. If you want to receive the previous values as well you can - enable full replication for the table you are listening too: - - ```sql - alter table "your_table" replica identity full; - ``` - dart: | - ```dart - final mySubscription = supabase - .from('countries') - .on(SupabaseEventTypes.update, (payload) { - // Handle realtime payload - }) - .subscribe(); - ``` - - name: Listening to deletes - description: | - By default, Supabase does not send deleted records. If you want to receive the deleted record you can - enable full replication for the table you are listening too: - - ```sql - alter table "your_table" replica identity full; - ``` - dart: | - ```dart - final mySubscription = supabase - .from('countries') - .on(SupabaseEventTypes.delete, (payload) { - // Handle realtime payload - }) - .subscribe(); - ``` - - name: Listening to multiple events - description: You can chain listeners if you want to listen to multiple events for each table. - dart: | - ```dart - final mySubscription = supabase - .from('countries') - .on(SupabaseEventTypes.insert, handleInsert) - .on(SupabaseEventTypes.delete, handleDelete) - .subscribe(); - ``` - - name: Listening to row level changes - description: You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match. - dart: | - ```dart - final mySubscription = supabase - .from('countries:id=eq.200') - .on(SupabaseEventTypes.update, handleRecordUpdated) - .subscribe(); - ``` - - removeSubscription(): - description: | - Removes an active subscription and returns the number of open connections. - title: 'removeSubscription()' - notes: | - - Removing subscriptions is a great way to maintain the performance of your project's database. Supabase will automatically handle cleanup 30 seconds after a user is disconnected, but unused subscriptions may cause degradation as more users are simultaneously subscribed. - examples: - - name: Remove a subscription - isSpotlight: true - dart: | - ```dart - supabase.removeSubscription(mySubscription); - ``` - - getSubscriptions(): - description: | - Returns an array of all your subscriptions. - title: 'getSubscriptions()' - examples: - - name: Get all subscriptions - isSpotlight: true - dart: | - ```dart - final subscriptions = supabase.getSubscriptions(); - ``` - - stream(): - description: | - Notifies of data at the queried table. - title: 'stream()' - notes: | - - `stream()` will emit the initial data as well as any further change on the database as `Stream` of `List>` by combining Postgrest and Realtime. - - Takes a list of primary key columns as its argument. - examples: - - name: Listening to a specific table - isSpotlight: true - dart: | - ```dart - supabase - .from('countries') - .stream(['id']) - .execute(); - ``` - - name: Listening to a specific rows within a table - description: | - You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match. - This syntax is the as how you can filter data in Realtime - dart: | - ```dart - supabase - .from('countries:id=eq.120') - .stream(['id']) - .execute(); - ``` - - name: With `order()` - dart: | - ```dart - supabase - .from('countries') - .stream(['id']) - .order('name', ascending: false) - .execute(); - ``` - - name: With `limit()` - dart: | - ```dart - supabase - .from('countries') - .stream(['id']) - .order('name', ascending: false) - .limit(10) - .execute(); - ``` - - storage.listBuckets(): - description: | - Retrieves the details of all Storage buckets within an existing product. - title: 'listBuckets()' - $ref: '@supabase/storage-js."lib/StorageBucketApi".StorageBucketApi.listBuckets' - notes: | - - Policy permissions required: - - `buckets` permissions: `select` - - `objects` permissions: none - examples: - - name: List buckets - isSpotlight: true - dart: | - ```dart - final res = await supabase - .storage - .listBuckets() - ``` - - storage.getBucket(): - description: | - Retrieves the details of an existing Storage bucket. - title: 'getBucket()' - $ref: '@supabase/storage-js."lib/StorageBucketApi".StorageBucketApi.getBucket' - notes: | - - Policy permissions required: - - `buckets` permissions: `select` - - `objects` permissions: none - examples: - - name: Get bucket - isSpotlight: true - dart: | - ```dart - final res = await supabase - .storage - .getBucket('avatars') - ``` - - storage.createBucket(): - description: | - Creates a new Storage bucket - title: 'createBucket()' - notes: | - - Policy permissions required: - - `buckets` permissions: `insert` - - `objects` permissions: none - examples: - - name: Create bucket - isSpotlight: true - dart: | - ```dart - final res = await supabase - .storage - .createBucket('avatars'); - ``` - - storage.emptyBucket(): - description: | - Removes all objects inside a single bucket. - title: 'emptyBucket()' - notes: | - - Policy permissions required: - - `buckets` permissions: `select` - - `objects` permissions: `select` and `delete` - examples: - - name: Empty bucket - isSpotlight: true - dart: | - ```dart - final res = await supabase - .storage - .emptyBucket('avatars'); - ``` - storage.updateBucket(): - description: | - Updates a new Storage bucket - title: 'updateBucket()' - notes: | - - Policy permissions required: - - `buckets` permissions: `update` - - `objects` permissions: none - examples: - - name: Update bucket - isSpotlight: true - dart: | - ```dart - final res = await supabase - .storage - .updateBucket('avatars', { public: false }); - ``` - - storage.deleteBucket(): - description: | - Deletes an existing bucket. A bucket can't be deleted with existing objects inside it. You must first `empty()` the bucket. - title: 'deleteBucket()' - notes: | - - Policy permissions required: - - `buckets` permissions: `select` and `delete` - - `objects` permissions: none - examples: - - name: Delete bucket - isSpotlight: true - dart: | - ```dart - final res = await supabase - .storage - .deleteBucket('avatars'); - ``` - - storage.from.upload(): - description: | - Uploads a file to an existing bucket. - title: 'from.upload()' - $ref: '@supabase/storage-js."lib/StorageFileApi".StorageFileApi.upload' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `insert` - examples: - - name: Upload file - isSpotlight: true - dart: | - ```dart - final avatarFile = File('path/to/file'); - final res = await supabase - .storage - .from('avatars') - .upload('public/avatar1.png', avatarFile, fileOptions: FileOptions( - cacheControl: '3600', - upsert: false - )); - ``` - - storage.from.update(): - description: | - Replaces an existing file at the specified path with a new one. - title: 'from.update()' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `update` and `select` - examples: - - name: Update file - isSpotlight: true - dart: | - ```dart - final avatarFile = File('path/to/file'); - final res = await supabase - .storage - .from('avatars') - .update('public/avatar1.png', avatarFile, fileOptions: FileOptions( - cacheControl: '3600', - upsert: false - )); - ``` - - storage.from.move(): - description: | - Moves an existing file, optionally renaming it at the same time. - title: 'from.move()' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `update` and `select` - examples: - - name: Move file - isSpotlight: true - dart: | - ```dart - final res = await supabase - .storage - .from('avatars') - .move('public/avatar1.png', 'private/avatar2.png'); - ``` - - storage.from.createSignedUrl(): - description: | - Create signed url to download file without requiring permissions. This URL can be valid for a set number of seconds. - title: 'from.createSignedUrl()' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `select` - examples: - - name: Create Signed URL - isSpotlight: true - dart: | - ```dart - final res = await supabase - .storage - .from('avatars') - .createSignedUrl('avatar1.png', 60); - - final signedURL = res.data; - ``` - - storage.from.getPublicUrl(): - description: | - Retrieve URLs for assets in public buckets - title: 'from.getPublicUrl()' - notes: | - - The bucket needs to be set to public, either via [updateBucket()](/docs/reference/javascript/storage-updatebucket) or by going to Storage on [app.supabase.com](https://app.supabase.com), clicking the overflow menu on a bucket and choosing "Make public" - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: none - examples: - - name: Returns the URL for an asset in a public bucket - isSpotlight: true - dart: | - ```dart - final res = supabase - .storage - .from('public-bucket') - .getPublicUrl('avatar1.png'); - - final publicURL = res.data; - ``` - - storage.from.download(): - description: | - Downloads a file. - title: 'from.download()' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `select` - examples: - - name: Download file - isSpotlight: true - dart: | - ```dart - final res = await supabase - .storage - .from('avatars') - .download('avatar1.png'); - ``` - - storage.from.remove(): - description: | - Deletes files within the same bucket - title: 'from.remove()' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `delete` and `select` - examples: - - name: Delete file - isSpotlight: true - dart: | - ```dart - final res = await supabase - .storage - .from('avatars') - .remove(['avatar1.png']); - ``` - - storage.from.list(): - description: | - Lists all the files within a bucket. - title: 'from.list()' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `select` - examples: - - name: List files in a bucket - isSpotlight: true - dart: | - ```dart - final res = await supabase - .storage - .from('avatars') - .list(); - ``` - Using Modifiers: - description: | - Modifiers can be used on `select()` queries. - - If a Stored Procedure returns a table response, you can also apply modifiers to the `rpc()` function. - - limit(): - description: | - Limits the result with the specified count. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .limit(1) - .execute(); - ``` - - name: With embedded resources - dart: | - ```dart - final res = await supabase - .from('countries') - .select('name, cities(name)') - .eq('name', 'United States') - .limit(1, foreignTable: 'cities' ) - .execute(); - ``` - - order(): - description: | - Orders the result with the specified column. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .order('id', ascending: false ) - .execute(); - ``` - - name: With embedded resources - dart: | - ```dart - final res = await supabase - .from('countries') - .select('name, cities(name)') - .eq('name', 'United States') - .order('name', foreignTable: 'cities') - .execute(); - ``` - - range(): - description: | - Limits the result to rows within the specified range, inclusive. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .range(0,3) - .execute(); - ``` - - single(): - description: | - Retrieves only one row from the result. Result must be one row (e.g. using limit), otherwise this will result in an error. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .single() - .execute(); - ``` - - Using Filters: - description: | - Filters can be used on `select()`, `update()`, and `delete()` queries. - - If a Stored Procedure returns a table response, you can also apply filters. - - ### Applying Filters - - You must apply your filters to the end of your query. For example: - - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .eq('name', 'The Shire') // Correct - .execute(); - - final res = await supabase - .from('cities') - .eq('name', 'The Shire') // Incorrect - .select('name, country_id') - .execute(); - ``` - - ### Chaining - - Filters can be chained together to produce advanced queries. For example: - - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .gte('population', 1000) - .lt('population', 10000) - .execute(); - ``` - - .or(): - description: | - Finds all rows satisfying at least one of the filters. - notes: | - - `.or()` expects you to use the raw [PostgREST syntax](https://postgrest.org/en/stable/api.html#horizontal-filtering-rows) for the filter names and values. - - ```dart - .or('id.in.(6,7),arraycol.cs.{"a","b"}') // Use Postgres list () and 'in' for in_ filter. Array {} and 'cs' for contains. - .or('id.in.(${mylist.join(',')}),arraycol.cs.{${mylistArray.join(',')}}') // You can insert a Dart list for list or array column. - .or('id.in.(${mylist.join(',')}),rangecol.cs.(${mylistRange.join(',')}]') // You can insert a Dart list for list or range column. - ``` - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .or('id.eq.20,id.eq.30') - .execute(); - ``` - - name: Use `or` with `and` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .or('id.gt.20,and(name.eq.New Zealand,name.eq.France)') - .execute(); - ``` - # TODO Not available at the moment, so would have to fix the bug on postgrest-dart first - # - name: Use `or` on foreign tables - # isSpotlight: true - # dart: | - # ```dart - # final res = await supabase - # .from('countries') - # .select('id, cities(*)') - # .or('name.eq.Wellington,name.eq.Paris', { foreignTable: "cities" }) - # ``` - - .not(): - description: | - Finds all rows which doesn't satisfy the filter. - notes: | - - `.not()` expects you to use the raw [PostgREST syntax](https://postgrest.org/en/stable/api.html#horizontal-filtering-rows) for the filter names and values. - - ```dart - .not('name','eq','Paris') - .not('arraycol','cs','{"a","b"}') // Use Postgres array {} for array column and 'cs' for contains. - .not('rangecol','cs','(1,2]') // Use Postgres range syntax for range column. - .not('id','in','(6,7)') // Use Postgres list () and 'in' for in_ filter. - .not('id','in','(${mylist.join(',')})') // You can insert a Dart list array. - ``` - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .not('name', 'eq', 'Paris') - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .not('name', 'eq', 'Paris') - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('cities') - .delete() - .not('name', 'eq', 'Paris') - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_cities) - .not('name', 'eq', 'Paris') - .execute(); - ``` - - .match(): - description: | - Finds all rows whose columns match the specified `query` object. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .match({'name': 'Beijing', 'country_id': 156}) - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .match({'name': 'Beijing', 'country_id': 156}) - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('cities') - .delete() - .match({'name': 'Beijing', 'country_id': 156}) - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_cities') - .match({'name': 'Beijing', 'country_id': 156}) - .execute(); - ``` - - .eq(): - description: | - Finds all rows whose value on the stated `column` exactly matches the specified `value`. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .eq('name', 'The shire') - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .eq('name', 'San Francisco') - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('cities') - .delete() - .eq('name', 'Mordor') - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_cities') - .eq('name', 'San Francisco') - .execute(); - ``` - - .neq(): - description: | - Finds all rows whose value on the stated `column` doesn't match the specified `value`. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .neq('name', 'The shire') - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .neq('name', 'San Francisco') - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('cities') - .delete() - .neq('name', 'Mordor') - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_cities') - .neq('name', 'Lagos') - .execute(); - ``` - - .gt(): - description: | - Finds all rows whose value on the stated `column` is greater than the specified `value`. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .gt('country_id', 250) - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .gt('country_id', 250) - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('cities') - .delete() - .gt('country_id', 250) - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_cities') - .gt('country_id', 250) - .execute(); - ``` - - .gte(): - description: | - Finds all rows whose value on the stated `column` is greater than or equal to the specified `value`. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .gte('country_id', 250) - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .gte('country_id', 250) - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('cities') - .delete() - .gte('country_id', 250) - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_cities') - .gte('country_id', 250) - .execute(); - ``` - - .lt(): - description: | - Finds all rows whose value on the stated `column` is less than the specified `value`. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .lt('country_id', 250) - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .lt('country_id', 250) - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('cities') - .delete() - .lt('country_id', 250) - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_cities') - .lt('country_id', 250) - .execute(); - ``` - - .lte(): - description: | - Finds all rows whose value on the stated `column` is less than or equal to the specified `value`. - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.lte' - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .lte('country_id', 250) - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .lte('country_id', 250) - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('cities') - .delete() - .lte('country_id', 250) - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_cities') - .lte('country_id', 250) - .execute(); - ``` - - .like(): - description: | - Finds all rows whose value in the stated `column` matches the supplied `pattern` (case sensitive). - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.like' - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .like('name', '%la%') - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .like('name', '%la%') - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('cities') - .delete() - .like('name', '%la%') - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_cities') - .like('name', '%la%') - .execute(); - ``` - - .ilike(): - description: | - Finds all rows whose value in the stated `column` matches the supplied `pattern` (case insensitive). - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .ilike('name', '%la%') - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .ilike('name', '%la%') - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('cities') - .delete() - .ilike('name', '%la%') - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_cities') - .ilike('name', '%la%') - .execute(); - ``` - - .is_(): - description: | - A check for exact equality (null, true, false), finds all rows whose value on the stated `column` exactly match the specified `value`. - - `is_` and `in_` filter methods are suffixed with `_` to avoid collisions with reserved keywords. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .is_('name', null) - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .is_('name', null) - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('cities') - .delete() - .is_('name', null) - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_cities') - .is_('name', null) - .execute(); - ``` - - .in_(): - description: | - Finds all rows whose value on the stated `column` is found on the specified `values`. - - `is_` and `in_` filter methods are suffixed with `_` to avoid collisions with reserved keywords. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .in_('name', ['Rio de Janeiro', 'San Francisco']) - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .in_('name', ['Rio de Janeiro', 'San Francisco']) - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('cities') - .delete() - .in_('name', ['Rio de Janeiro', 'San Francisco']) - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_cities') - .in_('name', ['Rio de Janeiro', 'San Francisco']) - .execute(); - ``` - - .contains(): - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('countries') - .select('name, id, main_exports') - .contains('main_exports', ['oil']) - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('countries') - .update({ 'name': 'Mordor' }) - .contains('main_exports', ['oil']) - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('countries') - .delete() - .contains('main_exports', ['oil']) - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_countries') - .contains('main_exports', ['oil']) - .execute(); - ``` - - .containedBy(): - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('countries') - .select('name, id, main_exports') - .containedBy('main_exports', ['cars', 'food', 'machine']) - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('countries') - .update({ 'name': 'Mordor' }) - .containedBy('main_exports', ['orks', 'surveillance', 'evil']) - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('countries') - .delete() - .containedBy('main_exports', ['cars', 'food', 'machine']) - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_countries') - .containedBy('main_exports', ['cars', 'food', 'machine']) - .execute(); - ``` - - .rangeLt(): - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('countries') - .select('name, id, population_range_millions') - .rangeLt('population_range_millions', '[150, 250]') - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('countries') - .update({ 'name': 'Mordor' }) - .rangeLt('population_range_millions', '[150, 250]') - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('countries') - .delete() - .rangeLt('population_range_millions', '[150, 250]') - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_countries') - .rangeLt('population_range_millions', '[150, 250]') - .execute(); - ``` - - .rangeGt(): - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('countries') - .select('name, id, population_range_millions') - .rangeGt('population_range_millions', '[150, 250]') - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('countries') - .update({ 'name': 'Mordor' }) - .rangeGt('population_range_millions', '[150, 250]') - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('countries') - .delete() - .rangeGt('population_range_millions', '[150, 250]') - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_countries') - .rangeGt('population_range_millions', '[150, 250]') - .execute(); - ``` - - .rangeGte(): - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('countries') - .select('name, id, population_range_millions') - .rangeGte('population_range_millions', '[150, 250]') - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('countries') - .update({ 'name': 'Mordor' }) - .rangeGte('population_range_millions', '[150, 250]') - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('countries') - .delete() - .rangeGte('population_range_millions', '[150, 250]') - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_countries') - .rangeGte('population_range_millions', '[150, 250]') - .execute(); - ``` - - .rangeLte(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.rangeLte' - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('countries') - .select('name, id, population_range_millions') - .rangeLte('population_range_millions', '[150, 250]') - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('countries') - .update({ 'name': 'Mordor' }) - .rangeLte('population_range_millions', '[150, 250]') - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('countries') - .delete() - .rangeLte('population_range_millions', '[150, 250]') - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_countries') - .rangeLte('population_range_millions', [150, 250]) - .execute(); - ``` - - .rangeAdjacent(): - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('countries') - .select('name, id, population_range_millions') - .rangeAdjacent('population_range_millions', '[70, 185]') - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('countries') - .update({ 'name': 'Mordor' }) - .rangeAdjacent('population_range_millions', '[70, 185]') - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('countries') - .delete() - .rangeAdjacent('population_range_millions', '[70, 185]') - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_countries') - .rangeAdjacent('population_range_millions', '[70, 185]') - .execute(); - ``` - - .overlaps(): - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('countries') - .select('name, id, main_exports') - .overlaps('main_exports', ['computers', 'minerals']) - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('countries') - .update({ 'name': 'Mordor' }) - .overlaps('main_exports', ['computers', 'minerals']) - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('countries') - .delete() - .overlaps('main_exports', ['computers', 'minerals']) - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_countries') - .overlaps('main_exports', ['computers', 'minerals']) - .execute(); - ``` - - .textSearch(): - description: | - Finds all rows whose tsvector value on the stated `column` matches to_tsquery(query). - examples: - - name: Text search - dart: | - ```dart - final res = await supabase - .from('quotes') - .select('catchphrase') - .textSearch('catchphrase', "'fat' & 'cat'", - config: 'english' - ) - .execute(); - ``` - - name: Basic normalization - description: Uses PostgreSQL's `plainto_tsquery` function. - dart: | - ```dart - final res = await supabase - .from('quotes') - .select('catchphrase') - .textSearch('catchphrase', "'fat' & 'cat'", - type: TextSearchType.plain, - config: 'english' - ) - .execute(); - ``` - - name: Full normalization - description: Uses PostgreSQL's `phraseto_tsquery` function. - dart: | - ```dart - final res = await supabase - .from('quotes') - .select('catchphrase') - .textSearch('catchphrase', "'fat' & 'cat'", - type: TextSearchType.phrase, - config: 'english' - ) - .execute(); - ``` - - name: Full normalization - description: | - Uses PostgreSQL's `websearch_to_tsquery` function. - This function will never raise syntax errors, which makes it possible to use raw user-supplied input for search, and can be used - with advanced operators. - - - `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery. - - `"quoted text"`: text inside quote marks will be converted to terms separated by <-> operators, as if processed by phraseto_tsquery. - - `OR`: the word “or” will be converted to the | operator. - - `-`: a dash will be converted to the ! operator. - - dart: | - ```dart - final res = await supabase - .from('quotes') - .select('catchphrase') - .textSearch('catchphrase', "'fat or cat'", - type: TextSearchType.websearch, - config: 'english' - ) - .execute(); - ``` - - .filter(): - description: | - Finds all rows whose `column` satisfies the filter. - notes: | - - `.filter()` expects you to use the raw [PostgREST syntax](https://postgrest.org/en/stable/api.html#horizontal-filtering-rows) for the filter names and values, so it should only be used as an escape hatch in case other filters don't work. - ```dart - .filter('arraycol','cs','{"a","b"}') // Use Postgres array {} and 'cs' for contains. - .filter('rangecol','cs','(1,2]') // Use Postgres range syntax for range column. - .filter('id','in','(6,7)') // Use Postgres list () and 'in' for in_ filter. - .filter('id','cs','{${mylist.join(',')}}') // You can insert a Dart array list. - ``` - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, country_id') - .filter('name', 'in', '("Paris","Tokyo")') - .execute(); - ``` - - name: With `update()` - dart: | - ```dart - final res = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .filter('name', 'in', '("Paris","Tokyo")') - .execute(); - ``` - - name: With `delete()` - dart: | - ```dart - final res = await supabase - .from('cities') - .delete() - .filter('name', 'in', '("Paris","Tokyo")') - .execute(); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final res = await supabase - .rpc('echo_all_cities') - .filter('name', 'in', '("Paris","Tokyo")') - ``` - - name: Filter embedded resources - dart: | - ```dart - final res = await supabase - .from('cities') - .select('name, countries ( name )') - .filter('countries.name', 'in', '("France","Japan")') - .execute(); - ``` diff --git a/spec/supabase_dart_v1--old.yml b/spec/supabase_dart_v1--old.yml deleted file mode 100644 index 6f3ef71a653..00000000000 --- a/spec/supabase_dart_v1--old.yml +++ /dev/null @@ -1,2280 +0,0 @@ -openref: 0.1 - -info: - id: reference/dart - title: Getting started - description: | - - Supabase Dart. - - definition: ../../spec/enrichments/tsdoc_v2/combined.json - slugPrefix: '/' - specUrl: https://github.com/supabase/supabase/edit/master/spec/supabase_dart_v1.yml - libraries: - - name: 'Dart' - id: 'dart' - version: '0.0.1' - -pages: - auth.signUp(): - title: 'signUp()' - description: | - Creates a new user. - notes: | - - By default, the user needs to verify their email address before logging in. To turn this off, disable **Confirm email** in [your project](https://app.supabase.com/project/_/auth/settings). - - **Confirm email** determines if users need to confirm their email address after signing up. - - If **Confirm email** is enabled, a `user` is returned but `session` is null. - - If **Confirm email** is disabled, both a `user` and a `session` are returned. - - When the user confirms their email address, they are redirected to the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url) by default. You can modify your `SITE_URL` or add additional redirect URLs in [your project](https://app.supabase.com/project/_/auth/settings). - - If signUp() is called for an existing confirmed user: - - If **Confirm email** is enabled in [your project](https://app.supabase.com/project/_/auth/settings), an obfuscated/fake user object is returned. - - If **Confirm email** is disabled, the error message, `User already registered` is returned. - examples: - - name: Sign up. - isSpotlight: true - dart: | - ```dart - final AuthResponse res = await supabase.auth.signUp( - email: 'example@email.com', - password: 'example-password', - ); - final Session? session = res.session; - final User? user = res.user; - ``` - - name: Sign up with third-party providers. - hideCodeBlock: true - description: | - If you are using Flutter, you can sign up with OAuth providers using the [`signInWithOAuth()`](/docs/reference/dart/auth-signinwithoauth) method available on `supabase_flutter`. - - auth.signInWithPassword(): - title: 'signInWithPassword()' - description: | - Log in an existing user using email or phone number with password. - notes: | - - Requires either an email and password or a phone number and password. - examples: - - name: Sign in with email and password - isSpotlight: true - dart: | - ```dart - final AuthResponse res = await supabase.auth.signInWithPassword( - email: 'example@email.com', - password: 'example-password', - ); - final Session? session = res.session; - final User? user = res.user; - ``` - - name: Sign in with phone and password - dart: | - ```dart - final AuthResponse res = await supabase.auth.signInWithPassword( - phone: '+13334445555', - password: 'example-password', - ); - final Session? session = res.session; - final User? user = res.user; - ``` - auth.signInWithOtp(): - title: 'signInWithOtp()' - notes: | - - Requires either an email or phone number. - - This method is used for passwordless sign-ins where a OTP is sent to the user's email or phone number. - - If you're using an email, you can configure whether you want the user to receive a magiclink or a OTP. - - If you're using phone, you can configure whether you want the user to receive a OTP. - - The magic link's destination URL is determined by the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url). You can modify the `SITE_URL` or add additional redirect urls in [your project](https://app.supabase.com/project/_/auth/settings). - examples: - - name: Sign in with email. - isSpotlight: true - description: | - The user will be sent an email which contains either a magiclink or a OTP or both. By default, a given user can only request a OTP once every 60 seconds. - You can pass `emailRedirectTo` with dynamic link to bring the users back to your app after they click on the magic link. - dart: | - ```dart - await supabase.auth.signInWithOtp( - email: 'example@email.com', - emailRedirectTo: kIsWeb ? null : 'io.supabase.flutter://signin-callback/', - ); - ``` - - name: Sign in with SMS OTP. - description: The user will be sent a SMS which contains a OTP. By default, a given user can only request a OTP once every 60 seconds. - dart: | - ```dart - await supabase.auth.signInWithOtp( - phone: '+13334445555', - ); - ``` - auth.signInWithOAuth(): - title: 'signInWithOAuth()' - description: | - Signs the user in using third party OAuth providers. - notes: | - - This method is used for signing in using a third-party provider. - - Supabase supports many different [third-party providers](https://supabase.com/docs/guides/auth#providers). - examples: - - name: Sign in using a third-party provider - isSpotlight: true - dart: | - ```dart - await supabase.auth.signInWithOAuth(Provider.github); - ``` - - name: With `redirectTo` - description: | - Specify the redirect link to bring back the user via deeplink. - Note that `redirectTo` should be null for Flutter Web. - dart: | - ```dart - await supabase.auth.signInWithOAuth( - Provider.github, - redirectTo: kIsWeb ? null : 'io.supabase.flutter://reset-callback/', - ); - ``` - - name: With scopes - description: | - If you need additional data from an OAuth provider, you can include a space-separated list of scopes in your request to get back an OAuth provider token. - You may also need to specify the scopes in the provider's OAuth app settings, depending on the provider. - dart: | - ```dart - await supabase.auth.signInWithOAuth( - Provider.github, - scopes: 'repo gist notifications' - ); - ... - // after user comes back from signin flow - - final Session? session = supabase.auth.currentSession; - final String? oAuthToken = session?.providerToken; - ``` - auth.signOut(): - title: 'signOut()' - description: | - Signs out the current user, if there is a logged in user. - notes: | - - In order to use the `signOut()` method, the user needs to be signed in first. - examples: - - name: Sign out - isSpotlight: true - dart: | - ```dart - await supabase.auth.signOut(); - ``` - auth.verifyOtp(): - title: 'verifyOtp()' - notes: | - - The `verifyOtp` method takes in different verification types. If a phone number is used, the type can either be `sms` or `phone_change`. If an email address is used, the type can be one of the following: `signup`, `magiclink`, `recovery`, `invite` or `email_change`. - - The verification type used should be determined based on the corresponding auth method called before `verifyOtp` to sign up / sign-in a user. - examples: - - name: Verify Sms One-Time Password (OTP) - isSpotlight: true - dart: | - ```dart - final AuthResponse res = await supabase.auth.verifyOTP( - type: OtpType.sms, - token: '111111', - phone: '+13334445555', - ); - final Session? session = res.session; - final User? user = res.user; - ``` - - name: Verify Signup One-Time Password (OTP) - isSpotlight: false - dart: | - ```dart - final AuthResponse res = await supabase.auth.verifyOTP( - type: OtpType.signup, - token: token, - phone: '+13334445555', - ); - final Session? session = res.session; - final User? user = res.user; - ``` - auth.currentSession: - title: 'currentSession' - description: | - Returns the session data, if there is an active session. - examples: - - name: Get the session data - isSpotlight: true - dart: | - ```dart - final Session? session = supabase.auth.currentSession; - ``` - auth.currentUser: - title: 'currentUser' - description: | - Returns the user data, if there is a logged in user. - examples: - - name: Get the logged in user - isSpotlight: true - dart: | - ```dart - final User? user = supabase.auth.currentUser; - ``` - auth.updateUser(): - title: 'updateUser()' - description: | - Updates user data, if there is a logged in user. - notes: | - - In order to use the `updateUser()` method, the user needs to be signed in first. - - By Default, email updates sends a confirmation link to both the user's current and new email. - To only send a confirmation link to the user's new email, disable **Secure email change** in your project's [email auth provider settings](https://app.supabase.com/project/_/auth/settings). - examples: - - name: Update the email for an authenticated user - description: Sends a "Confirm Email Change" email to the new email address. - isSpotlight: true - dart: | - ```dart - final UserResponse res = await supabase.auth.updateUser( - UserAttributes( - email: 'example@email.com', - ), - ); - final User? updatedUser = res.user; - ``` - - name: Update the password for an authenticated user - isSpotlight: false - dart: | - ```dart - final UserResponse res = await supabase.auth.updateUser( - UserAttributes( - password: 'new password', - ), - ); - final User? updatedUser = res.user; - ``` - - name: Update the user's metadata - isSpotlight: true - dart: | - ```dart - final UserResponse res = await supabase.auth.updateUser( - UserAttributes( - data: { 'hello': 'world' }, - ), - ); - final User? updatedUser = res.user; - ``` - auth.onAuthStateChange(): - title: 'onAuthStateChange()' - description: | - Receive a notification every time an auth event happens. - notes: | - - Types of auth events: `AuthChangeEvent.passwordRecovery`, `AuthChangeEvent.signedIn`, `AuthChangeEvent.signedOut`, `AuthChangeEvent.tokenRefreshed`, `AuthChangeEvent.userUpdated`and `AuthChangeEvent.userDeleted` - examples: - - name: Listen to auth changes - isSpotlight: true - dart: | - ```dart - final authSubscription = supabase.auth.onAuthStateChange.listen((data) { - final AuthChangeEvent event = data.event; - final Session? session = data.session; - }); - ``` - - name: Listen to a specific event - dart: | - ```dart - final authSubscription = supabase.auth.onAuthStateChange.listen((data) { - final AuthChangeEvent event = data.event; - if (event == AuthChangeEvent.signedIn) { - // handle signIn - } - }); - ``` - - name: Unsubscribe from auth subscription - dart: | - ```dart - final authSubscription = supabase.auth.onAuthStateChange((event, session) {}); - - authSubscription.cancel(); - ``` - auth.resetPasswordForEmail: - title: 'resetPasswordForEmail()' - description: | - Sends a reset request to an email address. - notes: | - Sends a password reset request to an email address. When the user clicks the reset link in the email they are redirected back to your application. Prompt the user for a new password and call auth.updateUser(): - - ```dart - await supabase.auth.resetPasswordForEmail( - 'sample@email.com', - redirectTo: kIsWeb ? null : 'io.supabase.flutter://reset-callback/', - ); - ``` - examples: - - name: Reset password for Flutter - isSpotlight: true - dart: | - `redirectTo` is used to open the app via deeplink when user opens the password reset email. - ```dart - await supabase.auth.resetPasswordForEmail( - 'sample@email.com', - redirectTo: kIsWeb ? null : 'io.supabase.flutter://reset-callback/', - ); - ``` - invoke(): - title: 'invoke()' - description: | - Invokes a Supabase Function. See the [guide](/docs/guides/functions) for details on writing Functions. - notes: | - - Requires an Authorization header. - - Invoke params generally match the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) spec. - examples: - - name: Basic invocation. - isSpotlight: true - dart: | - ```dart - final res = await supabase.functions.invoke('hello', body: {'foo': 'baa'}); - final data = res.data; - ``` - - name: Specifying response type. - description: | - By default, `invoke()` will parse the response as JSON. You can parse the response in the following formats: `json`, `blob`, `text`, and `arrayBuffer`. - isSpotlight: true - dart: | - ```dart - final res = await supabase.functions.invoke( - 'hello', - body: {'foo': 'baa'}, - responseType: ResponseType.text, - ); - final data = res.data; - ``` - - name: Parsing custom headers. - description: | - Any `headers` will be passed through to the function. A common pattern is to pass a logged-in user's JWT token as an Authorization header. - isSpotlight: true - dart: | - ```dart - final res = await supabase.functions.invoke( - 'hello', - body: {'foo': 'baa'}, - headers: { - 'Authorization': 'Bearer ${supabase.auth.currentSession?.accessToken}' - }, - ); - ``` - select(): - description: | - Performs vertical filtering with SELECT. - title: 'Fetch data: select()' - notes: | - - By default, Supabase projects will return a maximum of 1,000 rows. This setting can be changed in Project API Settings. It's recommended that you keep it low to limit the payload size of accidental or malicious requests. You can use `range()` queries to paginate through your data. - - `select()` can be combined with [Modifiers](/docs/reference/dart/using-modifiers) - - `select()` can be combined with [Filters](/docs/reference/dart/using-filters) - - If using the Supabase hosted platform `apikey` is technically a reserved keyword, since the API gateway will pluck it out for authentication. [It should be avoided as a column name](https://github.com/supabase/supabase/issues/5465). - examples: - - name: Getting your data - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name'); - ``` - - name: Selecting specific columns - description: You can select specific fields from your tables. - dart: | - ```dart - final data = await supabase - .from('countries') - .select(''' - name, - cities ( - name - ) - '''); - ``` - - name: Query foreign tables - description: If your database has relationships, you can query related tables too. - dart: | - ```dart - final data = await supabase - .from('products') - .select(''' - id, - supplier:supplier_id ( name ), - purchaser:purchaser_id ( name ) - '''); - ``` - - name: Query the same foreign table multiple times - description: | - Sometimes you will need to query the same foreign table twice. - In this case, you can use the name of the joined column to identify - which join you intend to use. For convenience, you can also give an - alias for each column. For example, if we had a shop of products, - and we wanted to get the supplier and the purchaser at the same time - (both in the users) table: - dart: | - ```dart - final data = await supabase - .from('messages') - .select('*, users!inner(*)') - .eq('users.username', 'Jane'); - ``` - - name: Filtering with inner joins - description: | - If you want to filter a table based on a child table's values you can use the `!inner()` function. For example, if you wanted - to select all rows in a `message` table which belong to a user with the `username` "Jane": - dart: | - ```dart - final data = await supabase - .from('messages') - .select('*, users!inner(*)') - .eq('users.username', 'Jane'); - ``` - - name: Querying with count option - description: | - You can get the number of rows by using the count option. - Allowed values for count option are [exact](https://postgrest.org/en/stable/api.html#exact-count), [planned](https://postgrest.org/en/stable/api.html#planned-count) and [estimated](https://postgrest.org/en/stable/api.html#estimated-count). - dart: | - ```dart - final res = await supabase.from('cities').select( - 'name', - const FetchOptions( - count: CountOption.exact, - ), - ); - - final count = res.count; - ``` - - name: Querying JSON data - description: | - If you have data inside of a JSONB column, you can apply select - and query filters to the data values. Postgres offers a - [number of operators](https://www.postgresql.org/docs/current/functions-json.html) - for querying JSON data. Also see - [PostgREST docs](http://postgrest.org/en/v7.0.0/api.html#json-columns) for more details. - dart: | - ```dart - final data = await supabase - .from('users') - .select(''' - id, name, - address->street - ''') - .eq('address->postcode', 90210); - ``` - - name: Return data as CSV - description: | - By default the data is returned in JSON format, however you can also request for it to be returned as Comma Separated Values. - dart: | - ```dart - final data = await supabase - .from('users') - .select() - .csv(); - ``` - - insert(): - description: | - Performs an INSERT into the table. - title: 'Create data: insert()' - examples: - - name: Create a record - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .insert({'name': 'The Shire', 'country_id': 554}); - ``` - - name: Bulk create - dart: | - ```dart - final res = await supabase.from('cities').insert([ - {'name': 'The Shire', 'country_id': 554}, - {'name': 'Rohan', 'country_id': 555}, - ]); - ``` - - update(): - description: | - Performs an UPDATE on the table. - title: 'Modify data: update()' - notes: | - - `update()` should always be combined with [Filters](/docs/reference/dart/using-filters) to target the item(s) you wish to update. - examples: - - name: Updating your data - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .update({ 'name': 'Middle Earth' }) - .match({ 'name': 'Auckland' }); - ``` - - name: Updating JSON data - description: | - Postgres offers a - [number of operators](https://www.postgresql.org/docs/current/functions-json.html) - for working with JSON data. Right now it is only possible to update an entire JSON document, - but we are [working on ideas](https://github.com/PostgREST/postgrest/issues/465) for updating individual keys. - dart: | - ```dart - final data = await supabase - .from('users') - .update({ - 'address': { - 'street': 'Melrose Place', - 'postcode': 90210 - } - }) - .eq('address->postcode', 90210); - ``` - - upsert(): - description: | - Performs an UPSERT into the table. - title: 'Upsert data: upsert()' - notes: | - - Primary keys should be included in the data payload in order for an update to work correctly. - - Primary keys must be natural, not surrogate. There are however, [workarounds](https://github.com/PostgREST/postgrest/issues/1118) for surrogate primary keys. - examples: - - name: Upsert your data - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('messages') - .upsert({ 'id': 3, 'message': 'foo', 'username': 'supabot' }); - ``` - - name: Upserting into tables with constraints - description: | - Running the following will cause supabase to upsert data into the `users` table. - If the username 'supabot' already exists, the `onConflict` argument tells supabase to overwrite that row - based on the column passed into `onConflict`. - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('users') - .upsert({ 'username': 'supabot' }, { 'onConflict': 'username' }); - ``` - - name: Return the exact number of rows - description: | - Allowed values for count option are `exact`, `planned` and `estimated`. - dart: | - ```dart - final res = await supabase.from('users').upsert( - {'id': 3, 'message': 'foo', 'username': 'supabot'}, - options: const FetchOptions(count: CountOption.exact), - ); - - final data = res.data; - final count = res.count; - ``` - - delete(): - description: | - Performs a DELETE on the table. - title: 'Delete data: delete()' - notes: | - - `delete()` should always be combined with [Filters](/docs/reference/dart/using-filters) to target the item(s) you wish to delete. - examples: - - name: Delete records - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .delete() - .match({ 'id': 666 }); - ``` - - rpc(): - title: 'Stored Procedures: rpc()' - description: | - You can call stored procedures as a "Remote Procedure Call". - - That's a fancy way of saying that you can put some logic into your database then call it from anywhere. - It's especially useful when the logic rarely changes - like password resets and updates. - examples: - - name: Call a stored procedure - isSpotlight: true - description: This is an example invoking a stored procedure. - dart: | - ```dart - final data = await supabase - .rpc('hello_world'); - ``` - - name: With Parameters - dart: | - ```dart - final data = await supabase - .rpc('echo_city', params: { 'name': 'The Shire' }); - ``` - - subscribe(): - description: | - Subscribe to realtime changes in your database. - title: 'on().subscribe()' - notes: | - - Realtime is disabled by default for new Projects for better database performance and security. You can turn it on by [managing replication](/docs/guides/api#managing-realtime). - - If you want to receive the "previous" data for updates and deletes, you will need to set `REPLICA IDENTITY` to `FULL`, like this: `ALTER TABLE your_table REPLICA IDENTITY FULL;` - examples: - - name: Listen to all database changes - isSpotlight: true - dart: | - ```dart - supabase.channel('*').on( - RealtimeListenTypes.postgresChanges, - ChannelFilter(event: '*', schema: '*'), - (payload, [ref]) { - print('Change received: ${payload.toString()}'); - }, - ).subscribe(); - ``` - - name: Listening to a specific table - dart: | - ```dart - supabase.channel('public:countries').on( - RealtimeListenTypes.postgresChanges, - ChannelFilter(event: '*', schema: 'public', table: 'countries'), - (payload, [ref]) { - print('Change received: ${payload.toString()}'); - }, - ).subscribe(); - ``` - - name: Listening to inserts - dart: | - ```dart - supabase.channel('public:countries').on( - RealtimeListenTypes.postgresChanges, - ChannelFilter(event: 'INSERT', schema: 'public', table: 'countries'), - (payload, [ref]) { - print('Change received: ${payload.toString()}'); - }, - ).subscribe(); - ``` - - name: Listening to updates - description: | - By default, Supabase will send only the updated record. If you want to receive the previous values as well you can - enable full replication for the table you are listening too: - - ```sql - alter table "your_table" replica identity full; - ``` - dart: | - ```dart - supabase.channel('public:countries').on( - RealtimeListenTypes.postgresChanges, - ChannelFilter(event: 'UPDATE', schema: 'public', table: 'countries'), - (payload, [ref]) { - print('Change received: ${payload.toString()}'); - }, - ).subscribe(); - ``` - - name: Listening to deletes - description: | - By default, Supabase does not send deleted records. If you want to receive the deleted record you can - enable full replication for the table you are listening too: - - ```sql - alter table "your_table" replica identity full; - ``` - dart: | - ```dart - supabase.channel('public:countries').on( - RealtimeListenTypes.postgresChanges, - ChannelFilter(event: 'DELETE', schema: 'public', table: 'countries'), - (payload, [ref]) { - print('Change received: ${payload.toString()}'); - }, - ).subscribe(); - ``` - - name: Listening to multiple events - description: You can chain listeners if you want to listen to multiple events for each table. - dart: | - ```dart - supabase.channel('public:countries').on(RealtimeListenTypes.postgresChanges, - ChannelFilter(event: 'INSERT', schema: 'public', table: 'countries'), - (payload, [ref]) { - print('Change received: ${payload.toString()}'); - }).on(RealtimeListenTypes.postgresChanges, - ChannelFilter(event: 'DELETE', schema: 'public', table: 'countries'), - (payload, [ref]) { - print('Change received: ${payload.toString()}'); - }).subscribe(); - ``` - - name: Listening to row level changes - description: You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match. - dart: | - ```dart - supabase.channel('public:countries:id=eq.200').on( - RealtimeListenTypes.postgresChanges, - ChannelFilter( - event: 'UPDATE', - schema: 'public', - table: 'countries', - filter: 'id=eq.200', - ), (payload, [ref]) { - print('Change received: ${payload.toString()}'); - }).subscribe(); - ``` - - removeChannel(): - description: | - Unsubscribes and removes Realtime channel from Realtime client. - title: 'removeChannel()' - notes: | - - Removing a channel is a great way to maintain the performance of your project's Realtime service as well as your database if you're listening to Postgres changes. Supabase will automatically handle cleanup 30 seconds after a client is disconnected, but unused channels may cause degradation as more clients are simultaneously subscribed. - examples: - - name: Remove a channel - isSpotlight: true - dart: | - ```dart - final status = await supabase.removeChannel(channel); - ``` - - removeAllChannels(): - description: | - Unsubscribes and removes all Realtime channels from Realtime client. - title: 'removeAllChannels()' - notes: | - - Removing channels is a great way to maintain the performance of your project's Realtime service as well as your database if you're listening to Postgres changes. Supabase will automatically handle cleanup 30 seconds after a client is disconnected, but unused channels may cause degradation as more clients are simultaneously subscribed. - examples: - - name: Remove all channels - isSpotlight: true - dart: | - ```dart - final statuses = await supabase.removeAllChannels(); - ``` - - getChannels(): - description: | - Returns all Realtime channels. - title: 'getChannels()' - examples: - - name: Get all channels - isSpotlight: true - dart: | - ```dart - final channels = supabase.getChannels(); - ``` - - stream(): - description: | - Notifies of data at the queried table. - title: 'stream()' - notes: | - - `stream()` will emit the initial data as well as any further change on the database as `Stream` of `List>` by combining Postgrest and Realtime. - - Takes a list of primary key columns as its argument. - examples: - - name: Listening to a specific table - isSpotlight: true - dart: | - ```dart - supabase.from('countries') - .stream(primaryKey: ['id']) - .listen((List> data) { - // Do something awesome with the data - }); - ``` - - name: Listening to a specific rows within a table - description: | - You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match. - This syntax is the as how you can filter data in Realtime - dart: | - ```dart - supabase.from('countries') - .stream(primaryKey: ['id']) - .eq('id', '120') - .listen((List> data) { - // Do something awesome with the data - }); - ``` - - name: With `order()` - dart: | - ```dart - supabase.from('countries') - .stream(primaryKey: ['id']) - .order('name', ascending: true) - .listen((List> data) { - // Do something awesome with the data - }); - ``` - - name: With `limit()` - dart: | - ```dart - supabase.from('countries') - .stream(primaryKey: ['id']) - .order('name', ascending: true) - .limit(10) - .listen((List> data) { - // Do something awesome with the data - }); - ``` - - name: Using `stream()` with `StreamBuilder` - description: | - When using `stream()` with a `StreamBuilder` within your Flutter application, make sure to store your stream in a variable to prevent refetching upon rebuilding. - dart: | - ```dart - final supabase = Supabase.instance.client; - - class MyWidget extends StatefulWidget { - const MyWidget({Key? key}) : super(key: key); - - @override - State createState() => _MyWidgetState(); - } - - class _MyWidgetState extends State { - // Persist the stream in a local variable to prevent refetching upon rebuilds - final _stream = supabase.from('countries').stream(primaryKey: ['id']); - - @override - Widget build(BuildContext context) { - return StreamBuilder( - stream: _stream, - builder: (context, snapshot) { - // Return your widget with the data from the snapshot - }, - ); - } - } - ``` - - storage.listBuckets(): - description: | - Retrieves the details of all Storage buckets within an existing product. - title: 'listBuckets()' - notes: | - - Policy permissions required: - - `buckets` permissions: `select` - - `objects` permissions: none - examples: - - name: List buckets - isSpotlight: true - dart: | - ```dart - final List buckets = await supabase - .storage - .listBuckets(); - ``` - - storage.getBucket(): - description: | - Retrieves the details of an existing Storage bucket. - title: 'getBucket()' - notes: | - - Policy permissions required: - - `buckets` permissions: `select` - - `objects` permissions: none - examples: - - name: Get bucket - isSpotlight: true - dart: | - ```dart - final Bucket bucket = await supabase - .storage - .getBucket('avatars'); - ``` - - storage.createBucket(): - description: | - Creates a new Storage bucket - title: 'createBucket()' - notes: | - - Policy permissions required: - - `buckets` permissions: `insert` - - `objects` permissions: none - examples: - - name: Create bucket - isSpotlight: true - dart: | - ```dart - final String bucketId = await supabase - .storage - .createBucket('avatars'); - ``` - - storage.emptyBucket(): - description: | - Removes all objects inside a single bucket. - title: 'emptyBucket()' - notes: | - - Policy permissions required: - - `buckets` permissions: `select` - - `objects` permissions: `select` and `delete` - examples: - - name: Empty bucket - isSpotlight: true - dart: | - ```dart - final String result = await supabase - .storage - .emptyBucket('avatars'); - ``` - storage.updateBucket(): - description: | - Updates a new Storage bucket - title: 'updateBucket()' - notes: | - - Policy permissions required: - - `buckets` permissions: `update` - - `objects` permissions: none - examples: - - name: Update bucket - isSpotlight: true - dart: | - ```dart - final res = await supabase - .storage - .updateBucket('avatars', const BucketOptions(public: false)); - ``` - - storage.deleteBucket(): - description: | - Deletes an existing bucket. A bucket can't be deleted with existing objects inside it. You must first `empty()` the bucket. - title: 'deleteBucket()' - notes: | - - Policy permissions required: - - `buckets` permissions: `select` and `delete` - - `objects` permissions: none - examples: - - name: Delete bucket - isSpotlight: true - dart: | - ```dart - final String result = await supabase - .storage - .deleteBucket('avatars'); - ``` - - storage.from.upload(): - description: | - Uploads a file to an existing bucket. - title: 'from.upload()' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `insert` - examples: - - name: Upload file - isSpotlight: true - dart: | - ```dart - final avatarFile = File('path/to/file'); - final String path = await supabase.storage.from('avatars').upload( - 'public/avatar1.png', - avatarFile, - fileOptions: const FileOptions(cacheControl: '3600', upsert: false), - ); - ``` - - storage.from.update(): - description: | - Replaces an existing file at the specified path with a new one. - title: 'from.update()' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `update` and `select` - examples: - - name: Update file - isSpotlight: true - dart: | - ```dart - final avatarFile = File('path/to/local/file'); - final String path = await supabase.storage.from('avatars').update( - 'public/avatar1.png', - avatarFile, - fileOptions: const FileOptions(cacheControl: '3600', upsert: false), - ); - ``` - - storage.from.move(): - description: | - Moves an existing file, optionally renaming it at the same time. - title: 'from.move()' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `update` and `select` - examples: - - name: Move file - isSpotlight: true - dart: | - ```dart - final String result = await supabase - .storage - .from('avatars') - .move('public/avatar1.png', 'private/avatar2.png'); - ``` - - storage.from.createSignedUrl(): - description: | - Create signed url to download file without requiring permissions. This URL can be valid for a set number of seconds. - title: 'from.createSignedUrl()' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `select` - examples: - - name: Create Signed URL - isSpotlight: true - dart: | - ```dart - final String signedUrl = await supabase - .storage - .from('avatars') - .createSignedUrl('avatar1.png', 60); - ``` - - storage.from.createSignedUrls(): - title: 'from.createSignedUrls()' - notes: | - - RLS policy permissions required: - - `buckets` table permissions: none - - `objects` table permissions: `select` - - Refer to the [Storage guide](/docs/guides/storage#access-control) on how access control works - examples: - - name: Create Signed URLs - isSpotlight: true - dart: | - ```dart - final List signedUrls = await supabase - .storage - .from('avatars') - .createSignedUrls(['folder/avatar1.png', 'folder/avatar2.png'], 60); - ``` - - storage.from.getPublicUrl(): - description: | - Retrieve URLs for assets in public buckets - title: 'from.getPublicUrl()' - notes: | - - The bucket needs to be set to public, either via [updateBucket()](/docs/reference/javascript/storage-updatebucket) or by going to Storage on [app.supabase.com](https://app.supabase.com), clicking the overflow menu on a bucket and choosing "Make public" - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: none - examples: - - name: Returns the URL for an asset in a public bucket - isSpotlight: true - dart: | - ```dart - final String publicUrl = supabase - .storage - .from('public-bucket') - .getPublicUrl('avatar1.png'); - ``` - - storage.from.download(): - description: | - Downloads a file. - title: 'from.download()' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `select` - examples: - - name: Download file - isSpotlight: true - dart: | - ```dart - final Uint8List file = await supabase - .storage - .from('avatars') - .download('avatar1.png'); - ``` - - storage.from.remove(): - description: | - Deletes files within the same bucket - title: 'from.remove()' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `delete` and `select` - examples: - - name: Delete file - isSpotlight: true - dart: | - ```dart - final List objects = await supabase - .storage - .from('avatars') - .remove(['avatar1.png']); - ``` - - storage.from.list(): - description: | - Lists all the files within a bucket. - title: 'from.list()' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `select` - examples: - - name: List files in a bucket - isSpotlight: true - dart: | - ```dart - final List objects = await supabase - .storage - .from('avatars') - .list(); - ``` - Using Modifiers: - description: | - Filters work on the row level—they allow you to return rows that - only match certain conditions without changing the shape of the rows. - Modifiers are everything that don't fit that definition—allowing you to - change the format of the response (e.g., returning a CSV string). - - Modifiers must be specified after filters. Some modifiers only apply for - queries that return rows (e.g., `select()` or `rpc()` on a function that - returns a table response). - - limit(): - description: | - Limits the result with the specified count. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .limit(1); - ``` - - name: With embedded resources - dart: | - ```dart - final data = await supabase - .from('countries') - .select('name, cities(name)') - .eq('name', 'United States') - .limit(1, foreignTable: 'cities' ); - ``` - - order(): - description: | - Orders the result with the specified column. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .order('id', ascending: false ); - ``` - - name: With embedded resources - dart: | - ```dart - final data = await supabase - .from('countries') - .select('name, cities(name)') - .eq('name', 'United States') - .order('name', foreignTable: 'cities'); - ``` - - range(): - description: | - Limits the result to rows within the specified range, inclusive. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .range(0,3); - ``` - - single(): - description: | - Retrieves only one row from the result. Result must be one row (e.g. using limit), otherwise this will result in an error. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .single(); - ``` - - Using Filters: - description: | - Filters allow you to only return rows that match certain conditions. - - Filters can be used on `select()`, `update()`, and `delete()` queries. - - If a Database function returns a table response, you can also apply filters. - - ### Applying Filters - - Filters must be applied after any of `select()`, `update()`, `upsert()`, - `delete()`, and `rpc()` and before - [modifiers](/docs/reference/dart/using-modifiers). - - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .eq('name', 'The Shire'); // Correct - - final data = await supabase - .from('cities') - .eq('name', 'The Shire') // Incorrect - .select('name, country_id'); - ``` - - ### Chaining - - Filters can be chained together to produce advanced queries. For example, - to query cities with population between 1,000 and 10,000: - - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .gte('population', 1000) - .lt('population', 10000); - ``` - - ### Conditional Chaining - - Filters can be built up one step at a time and then executed. For example: - - ```dart - final filterByName = null; - final filterPopLow = 1000; - final filterPopHigh = 10000; - - var query = supabase - .from('cities') - .select('name, country_id'); - - if (filterByName != null) { query = query.eq('name', filterByName); } - if (filterPopLow != null) { query = query.gte('population', filterPopLow); } - if (filterPopHigh != null) { query = query.lt('population', filterPopHigh); } - - final data = await query; - ``` - - ### Filter by values within a JSON column - - - - - ```sql - create table - users ( - id int8 primary key, - name text, - address jsonb - ); - - insert into - users (id, name, address) - values - (1, 'Michael', '{ "postcode": 90210 }'), - (2, 'Jane', null); - ``` - - - - - ```dart - final data = await supabase - .from('users') - .select() - .eq('address->postcode', 90210); - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Michael", - "address": { - "postcode": 90210 - } - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - - ### Filter Foreign Tables - - You can filter on foreign tables in your `select()` query using dot - notation: - - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'Germany'), - (2, 'Indonesia'); - insert into - cities (id, country_id, name) - values - (1, 2, 'Bali'), - (2, 1, 'Munich'); - ``` - - - - - ```dart - final data = await supabase - .from('countries') - .select(''' - name, - cities!inner ( - name - ) - ''') - .eq('cities.name', 'Bali'); - ``` - - - - - ```json - { - "data": [ - { - "name": "Indonesia", - "cities": [ - { - "name": "Bali" - } - ] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - - .or(): - description: | - Finds all rows satisfying at least one of the filters. - notes: | - - `.or()` expects you to use the raw [PostgREST syntax](https://postgrest.org/en/stable/api.html#horizontal-filtering-rows) for the filter names and values. - - ```dart - .or('id.in.(6,7),arraycol.cs.{"a","b"}') // Use Postgres list () and 'in' for in_ filter. Array {} and 'cs' for contains. - .or('id.in.(${mylist.join(',')}),arraycol.cs.{${mylistArray.join(',')}}') // You can insert a Dart list for list or array column. - .or('id.in.(${mylist.join(',')}),rangecol.cs.(${mylistRange.join(',')}]') // You can insert a Dart list for list or range column. - ``` - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .or('id.eq.20,id.eq.30'); - ``` - - name: Use `or` with `and` - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .or('id.gt.20,and(name.eq.New Zealand,name.eq.France)'); - ``` - - .not(): - description: | - Finds all rows which doesn't satisfy the filter. - notes: | - - `.not()` expects you to use the raw [PostgREST syntax](https://postgrest.org/en/stable/api.html#horizontal-filtering-rows) for the filter names and values. - - ```dart - .not('name','eq','Paris') - .not('arraycol','cs','{"a","b"}') // Use Postgres array {} for array column and 'cs' for contains. - .not('rangecol','cs','(1,2]') // Use Postgres range syntax for range column. - .not('id','in','(6,7)') // Use Postgres list () and 'in' for in_ filter. - .not('id','in','(${mylist.join(',')})') // You can insert a Dart list array. - ``` - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .not('name', 'eq', 'Paris'); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .not('name', 'eq', 'Paris'); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('cities') - .delete() - .not('name', 'eq', 'Paris'); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_cities) - .not('name', 'eq', 'Paris'); - ``` - - .match(): - description: | - Finds all rows whose columns match the specified `query` object. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .match({'name': 'Beijing', 'country_id': 156}); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .match({'name': 'Beijing', 'country_id': 156}); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('cities') - .delete() - .match({'name': 'Beijing', 'country_id': 156}); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_cities') - .match({'name': 'Beijing', 'country_id': 156}); - ``` - - .eq(): - description: | - Finds all rows whose value on the stated `column` exactly matches the specified `value`. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .eq('name', 'The shire'); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .eq('name', 'San Francisco'); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('cities') - .delete() - .eq('name', 'Mordor'); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_cities') - .eq('name', 'San Francisco'); - ``` - - .neq(): - description: | - Finds all rows whose value on the stated `column` doesn't match the specified `value`. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .neq('name', 'The shire'); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .neq('name', 'San Francisco'); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('cities') - .delete() - .neq('name', 'Mordor'); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_cities') - .neq('name', 'Lagos'); - ``` - - .gt(): - description: | - Finds all rows whose value on the stated `column` is greater than the specified `value`. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .gt('country_id', 250); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .gt('country_id', 250); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('cities') - .delete() - .gt('country_id', 250); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_cities') - .gt('country_id', 250); - ``` - - .gte(): - description: | - Finds all rows whose value on the stated `column` is greater than or equal to the specified `value`. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .gte('country_id', 250); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .gte('country_id', 250); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('cities') - .delete() - .gte('country_id', 250); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_cities') - .gte('country_id', 250); - ``` - - .lt(): - description: | - Finds all rows whose value on the stated `column` is less than the specified `value`. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .lt('country_id', 250); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .lt('country_id', 250); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('cities') - .delete() - .lt('country_id', 250); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_cities') - .lt('country_id', 250); - ``` - - .lte(): - description: | - Finds all rows whose value on the stated `column` is less than or equal to the specified `value`. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .lte('country_id', 250); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .lte('country_id', 250); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('cities') - .delete() - .lte('country_id', 250); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_cities') - .lte('country_id', 250); - ``` - - .like(): - description: | - Finds all rows whose value in the stated `column` matches the supplied `pattern` (case sensitive). - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .like('name', '%la%'); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .like('name', '%la%'); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('cities') - .delete() - .like('name', '%la%'); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_cities') - .like('name', '%la%'); - ``` - - .ilike(): - description: | - Finds all rows whose value in the stated `column` matches the supplied `pattern` (case insensitive). - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .ilike('name', '%la%'); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .ilike('name', '%la%'); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('cities') - .delete() - .ilike('name', '%la%'); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_cities') - .ilike('name', '%la%'); - ``` - - .is_(): - description: | - A check for exact equality (null, true, false), finds all rows whose value on the stated `column` exactly match the specified `value`. - - `is_` and `in_` filter methods are suffixed with `_` to avoid collisions with reserved keywords. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .is_('name', null); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .is_('name', null); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('cities') - .delete() - .is_('name', null); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_cities') - .is_('name', null); - ``` - - .in_(): - description: | - Finds all rows whose value on the stated `column` is found on the specified `values`. - - `is_` and `in_` filter methods are suffixed with `_` to avoid collisions with reserved keywords. - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .in_('name', ['Rio de Janeiro', 'San Francisco']); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .in_('name', ['Rio de Janeiro', 'San Francisco']); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('cities') - .delete() - .in_('name', ['Rio de Janeiro', 'San Francisco']); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_cities') - .in_('name', ['Rio de Janeiro', 'San Francisco']); - ``` - - .contains(): - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('countries') - .select('name, id, main_exports') - .contains('main_exports', ['oil']); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('countries') - .update({ 'name': 'Mordor' }) - .contains('main_exports', ['oil']); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('countries') - .delete() - .contains('main_exports', ['oil']); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_countries') - .contains('main_exports', ['oil']); - ``` - - .containedBy(): - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('countries') - .select('name, id, main_exports') - .containedBy('main_exports', ['cars', 'food', 'machine']); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('countries') - .update({ 'name': 'Mordor' }) - .containedBy('main_exports', ['orks', 'surveillance', 'evil']); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('countries') - .delete() - .containedBy('main_exports', ['cars', 'food', 'machine']); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_countries') - .containedBy('main_exports', ['cars', 'food', 'machine']); - ``` - - .rangeLt(): - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('countries') - .select('name, id, population_range_millions') - .rangeLt('population_range_millions', '[150, 250]'); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('countries') - .update({ 'name': 'Mordor' }) - .rangeLt('population_range_millions', '[150, 250]'); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('countries') - .delete() - .rangeLt('population_range_millions', '[150, 250]'); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_countries') - .rangeLt('population_range_millions', '[150, 250]'); - ``` - - .rangeGt(): - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('countries') - .select('name, id, population_range_millions') - .rangeGt('population_range_millions', '[150, 250]'); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('countries') - .update({ 'name': 'Mordor' }) - .rangeGt('population_range_millions', '[150, 250]'); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('countries') - .delete() - .rangeGt('population_range_millions', '[150, 250]'); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_countries') - .rangeGt('population_range_millions', '[150, 250]'); - ``` - - .rangeGte(): - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('countries') - .select('name, id, population_range_millions') - .rangeGte('population_range_millions', '[150, 250]'); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('countries') - .update({ 'name': 'Mordor' }) - .rangeGte('population_range_millions', '[150, 250]'); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('countries') - .delete() - .rangeGte('population_range_millions', '[150, 250]'); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_countries') - .rangeGte('population_range_millions', '[150, 250]'); - ``` - - .rangeLte(): - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('countries') - .select('name, id, population_range_millions') - .rangeLte('population_range_millions', '[150, 250]'); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('countries') - .update({ 'name': 'Mordor' }) - .rangeLte('population_range_millions', '[150, 250]'); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('countries') - .delete() - .rangeLte('population_range_millions', '[150, 250]'); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_countries') - .rangeLte('population_range_millions', [150, 250]); - ``` - - .rangeAdjacent(): - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('countries') - .select('name, id, population_range_millions') - .rangeAdjacent('population_range_millions', '[70, 185]'); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('countries') - .update({ 'name': 'Mordor' }) - .rangeAdjacent('population_range_millions', '[70, 185]'); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('countries') - .delete() - .rangeAdjacent('population_range_millions', '[70, 185]'); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_countries') - .rangeAdjacent('population_range_millions', '[70, 185]'); - ``` - - .overlaps(): - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('countries') - .select('name, id, main_exports') - .overlaps('main_exports', ['computers', 'minerals']); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('countries') - .update({ 'name': 'Mordor' }) - .overlaps('main_exports', ['computers', 'minerals']); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('countries') - .delete() - .overlaps('main_exports', ['computers', 'minerals']); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_countries') - .overlaps('main_exports', ['computers', 'minerals']); - ``` - - .textSearch(): - description: | - Finds all rows whose tsvector value on the stated `column` matches to_tsquery(query). - examples: - - name: Text search - dart: | - ```dart - final data = await supabase - .from('quotes') - .select('catchphrase') - .textSearch('catchphrase', "'fat' & 'cat'", - config: 'english' - ); - ``` - - name: Basic normalization - description: Uses PostgreSQL's `plainto_tsquery` function. - dart: | - ```dart - final data = await supabase - .from('quotes') - .select('catchphrase') - .textSearch('catchphrase', "'fat' & 'cat'", - type: TextSearchType.plain, - config: 'english' - ); - ``` - - name: Full normalization - description: Uses PostgreSQL's `phraseto_tsquery` function. - dart: | - ```dart - final data = await supabase - .from('quotes') - .select('catchphrase') - .textSearch('catchphrase', "'fat' & 'cat'", - type: TextSearchType.phrase, - config: 'english' - ); - ``` - - name: Full normalization - description: | - Uses PostgreSQL's `websearch_to_tsquery` function. - This function will never raise syntax errors, which makes it possible to use raw user-supplied input for search, and can be used - with advanced operators. - - - `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery. - - `"quoted text"`: text inside quote marks will be converted to terms separated by <-> operators, as if processed by phraseto_tsquery. - - `OR`: the word “or” will be converted to the | operator. - - `-`: a dash will be converted to the ! operator. - - dart: | - ```dart - final data = await supabase - .from('quotes') - .select('catchphrase') - .textSearch('catchphrase', "'fat or cat'", - type: TextSearchType.websearch, - config: 'english' - ); - ``` - - .filter(): - description: | - Finds all rows whose `column` satisfies the filter. - notes: | - - `.filter()` expects you to use the raw [PostgREST syntax](https://postgrest.org/en/stable/api.html#horizontal-filtering-rows) for the filter names and values, so it should only be used as an escape hatch in case other filters don't work. - ```dart - .filter('arraycol','cs','{"a","b"}') // Use Postgres array {} and 'cs' for contains. - .filter('rangecol','cs','(1,2]') // Use Postgres range syntax for range column. - .filter('id','in','(6,7)') // Use Postgres list () and 'in' for in_ filter. - .filter('id','cs','{${mylist.join(',')}}') // You can insert a Dart array list. - ``` - examples: - - name: With `select()` - isSpotlight: true - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, country_id') - .filter('name', 'in', '("Paris","Tokyo")'); - ``` - - name: With `update()` - dart: | - ```dart - final data = await supabase - .from('cities') - .update({ 'name': 'Mordor' }) - .filter('name', 'in', '("Paris","Tokyo")'); - ``` - - name: With `delete()` - dart: | - ```dart - final data = await supabase - .from('cities') - .delete() - .filter('name', 'in', '("Paris","Tokyo")'); - ``` - - name: With `rpc()` - dart: | - ```dart - // Only valid if the Stored Procedure returns a table type. - final data = await supabase - .rpc('echo_all_cities') - .filter('name', 'in', '("Paris","Tokyo")'); - ``` - - name: Filter embedded resources - dart: | - ```dart - final data = await supabase - .from('cities') - .select('name, countries ( name )') - .filter('countries.name', 'in', '("France","Japan")'); - ``` diff --git a/spec/supabase_js_v1--old.yml b/spec/supabase_js_v1--old.yml deleted file mode 100644 index 0daa83daa92..00000000000 --- a/spec/supabase_js_v1--old.yml +++ /dev/null @@ -1,2533 +0,0 @@ -openref: 0.1 - -info: - id: reference/supabase-js - title: Supabase Client - description: | - - Supabase JavaScript. - - definition: ../../spec/enrichments/tsdoc_v1/combined.json - specUrl: https://github.com/supabase/supabase/edit/master/spec/supabase_js_v1.yml - slugPrefix: '/' - libraries: - - name: 'JavaScript' - id: 'js' - version: '0.0.1' - -pages: - auth.signUp(): - title: 'signUp()' - $ref: '@supabase/gotrue-js.GoTrueClient.signUp' - notes: | - - By default, the user will need to verify their email address before logging in. If you would like to change this, you can disable "Email Confirmations" by going to Authentication -> Settings on [app.supabase.com](https://app.supabase.com) - - If "Email Confirmations" is turned on, a `user` is returned but `session` will be null - - If "Email Confirmations" is turned off, both a `user` and a `session` will be returned - - When the user confirms their email address, they will be redirected to localhost:3000 by default. To change this, you can go to Authentication -> Settings on [app.supabase.com](https://app.supabase.com) - - If signUp() is called for an existing confirmed user: - - If "Enable email confirmations" is enabled on the "Authentication" -> "Settings" page, an obfuscated / fake user object will be returned. - - If "Enable email confirmations" is disabled, an error with a message "User already registered" will be returned. - - To check if a user already exists, refer to getUser(). - examples: - - name: Sign up. - isSpotlight: true - js: | - ```js - const { user, session, error } = await supabase.auth.signUp({ - email: 'example@email.com', - password: 'example-password', - }) - ``` - - name: Sign up with additional user meta data. - isSpotlight: true - js: | - ```js - const { user, session, error } = await supabase.auth.signUp( - { - email: 'example@email.com', - password: 'example-password', - }, - { - data: { - first_name: 'John', - age: 27, - } - } - ) - ``` - - name: Sign up with third-party providers. - hideCodeBlock: true - description: | - You can sign up with OAuth providers using the [`signIn()`](/docs/reference/javascript/v1/auth-signin#sign-in-using-third-party-providers) method. - - name: Sign up with Phone. - description: | - Supabase supports Phone Auth. After a user has verified their number, they can use the [`signIn()`](/docs/reference/javascript/v1/auth-signin#sign-in-using-phone) method. - js: | - ```js - const { user, session, error } = await supabase.auth.signUp({ - phone: '+13334445555', - password: 'some-password', - }) - - // After receiving an SMS with One Time Password. - let { session, error } = await supabase.auth.verifyOTP({ - phone: '+13334445555', - token: '123456', - }) - ``` - - auth.signIn(): - title: 'signIn()' - $ref: '@supabase/gotrue-js.GoTrueClient.signIn' - notes: | - - A user can sign up either via email or OAuth. - - If you provide `email` without a `password`, the user will be sent a magic link. - - The magic link's destination URL is determined by the SITE_URL config variable. To change this, you can go to Authentication -> Settings on [app.supabase.com](https://app.supabase.com) - - Specifying a `provider` will open the browser to the relevant login page. - examples: - - name: Sign in with email. - isSpotlight: true - js: | - ```js - const { user, session, error } = await supabase.auth.signIn({ - email: 'example@email.com', - password: 'example-password', - }) - ``` - - name: Sign in with magic link. - description: If no password is provided, the user will be sent a "magic link" to their email address, which they can click to open your application with a valid session. By default, a given user can only request a Magic Link once every 60 seconds. - js: | - ```js - const { user, session, error } = await supabase.auth.signIn({ - email: 'example@email.com' - }) - ``` - - name: Sign in using third-party providers. - description: Supabase supports many different [third-party providers](https://supabase.com/docs/guides/auth#providers). - js: | - ```js - const { user, session, error } = await supabase.auth.signIn({ - // provider can be 'github', 'google', 'gitlab', and more - provider: 'github' - }) - ``` - - name: Sign in with Phone. - description: Supabase supports Phone Auth. - js: | - ```js - const { user, session, error } = await supabase.auth.signIn({ - phone: '+13334445555', - password: 'some-password', - }) - ``` - - name: Sign in with redirect. - description: | - Note that the `redirectTo` param is only relevant for OAuth logins, where the login flow is managed by - the Auth server. If you are using email/phone logins you should set up your own redirects (within the email/sms template). - - Sometimes you want to control where the user is redirected to after they are logged in. Supabase supports this for - any URL path on your website (the URL must either be on the same domain as your Site URL [see Auth>Settings in dashboard], or must match one of the Additional Redirect URLs [also in Auth>Settings]). - js: | - ```js - const { user, session, error } = await supabase.auth.signIn({ - provider: 'github' - }, { - redirectTo: 'https://example.com/welcome' - }) - ``` - - name: Sign in with scopes. - description: | - If you need additional data from an OAuth provider, you can include a space-separated list of scopes in your request to get back an OAuth provider token. - You may also need to specify the scopes in the provider's OAuth app settings, depending on the provider. - js: | - ```js - const { user, session, error } = await supabase.auth.signIn({ - provider: 'github' - }, { - scopes: 'repo gist notifications' - }) - const oAuthToken = session.provider_token // use to access provider API - ``` - - name: Sign in using a refresh token (e.g. in React Native). - description: | - If you are completing a sign up or login in a React Native app you can pass the refresh token obtained from the provider to obtain a session. - js: | - ```js - // An example using Expo's `AuthSession` - const redirectUri = AuthSession.makeRedirectUri({ useProxy: false }); - const provider = 'google'; - - AuthSession.startAsync({ - authUrl: `https://MYSUPABASEAPP.supabase.co/auth/v1/authorize?provider=${provider}&redirect_to=${redirectUri}`, - returnUrl: redirectUri, - }).then(async (response: any) => { - if (!response) return; - const { user, session, error } = await supabase.auth.signIn({ - refreshToken: response.params?.refresh_token, - }); - }); - ``` - - auth.signOut(): - title: 'signOut()' - $ref: '@supabase/gotrue-js.GoTrueClient.signOut' - examples: - - name: Sign out - isSpotlight: true - js: | - ```js - const { error } = await supabase.auth.signOut() - ``` - - auth.session(): - title: 'session()' - $ref: '@supabase/gotrue-js.GoTrueClient.session' - examples: - - name: Get the session data - isSpotlight: true - js: | - ```js - const session = supabase.auth.session() - ``` - - auth.user(): - title: 'user()' - $ref: '@supabase/gotrue-js.GoTrueClient.user' - notes: | - This method gets the user object from memory. - examples: - - name: Get the logged in user - isSpotlight: true - js: | - ```js - const user = supabase.auth.user() - ``` - - auth.update(): - title: 'update()' - $ref: '@supabase/gotrue-js.GoTrueClient.update' - notes: | - User email: By Default, email updates sends a confirmation link to both the user's current and new email. - To only send a confirmation link to the user's new email, disable **Secure email change** in your project's [email auth provider settings](https://app.supabase.com/project/_/auth/settings). - - User metadata: It's generally better to store user data in a table within your public schema (i.e., `public.users`). - Use the `update()` method if you have data which rarely changes or is specific only to the logged in user. - examples: - - name: Update the email for an authenticated user - description: Sends a "Confirm Email Change" email to the new email address. - isSpotlight: true - js: | - ```js - const { user, error } = await supabase.auth.update({email: 'new@email.com'}) - ``` - - name: Update the password for an authenticated user - isSpotlight: true - js: | - ```js - const { user, error } = await supabase.auth.update({password: 'new password'}) - ``` - - name: Update the user's metadata - isSpotlight: true - js: | - ```js - const { user, error } = await supabase.auth.update({ - data: { hello: 'world' } - }) - ``` - - auth.setAuth(): - title: 'setAuth()' - $ref: '@supabase/gotrue-js.GoTrueClient.setAuth' - examples: - - name: Basic example. - description: This is most useful on server-side functions where you cannot log the user in, but have access to the user's access token. - isSpotlight: true - js: | - ```js - function apiFunction(req, res) { - // Assuming the access token was sent as a header "X-Supabase-Auth" - const { access_token } = req.get('X-Supabase-Auth') - - // You can now use it within a Supabase Client - const supabase = createClient("https://xyzcompany.supabase.co", "public-anon-key") - const { user, error } = supabase.auth.setAuth(access_token) - - // This client will now send requests as this user - const { data } = await supabase.from('your_table').select() - } - ``` - - name: With Express. - isSpotlight: true - js: | - ```js - - /** - * Make a request from the client to your server function - */ - async function makeApiRequest() { - const token = newClient.session()?.access_token - - await fetch('https://example.com/withAuth', { - method: 'GET', - withCredentials: true, - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - 'Authorization': bearer, // Your own auth - 'X-Supabase-Auth': token, // Set the Supabase user - } - }) - } - - /** - * Use the Auth token in your server-side function. - */ - async function apiFunction(req, res) { - const { access_token } = req.get('X-Supabase-Auth') - - // You can now use it within a Supabase Client - const supabase = createClient("https://xyzcompany.supabase.co", "public-anon-key") - const { user, error } = supabase.auth.setAuth(access_token) - - // This client will now send requests as this user - const { data } = await supabase.from('your_table').select() - } - ``` - - auth.onAuthStateChange(): - title: 'onAuthStateChange()' - $ref: '@supabase/gotrue-js.GoTrueClient.onAuthStateChange' - examples: - - name: Listen to auth changes - isSpotlight: true - js: | - ```js - supabase.auth.onAuthStateChange((event, session) => { - console.log(event, session) - }) - ``` - - name: Listen to sign in - js: | - ```js - supabase.auth.onAuthStateChange((event, session) => { - if (event == 'SIGNED_IN') console.log('SIGNED_IN', session) - }) - ``` - - name: Listen to sign out - js: | - ```js - supabase.auth.onAuthStateChange((event, session) => { - if (event == 'SIGNED_OUT') console.log('SIGNED_OUT', session) - }) - ``` - - name: Listen to token refresh - js: | - ```js - supabase.auth.onAuthStateChange((event, session) => { - if (event == 'TOKEN_REFRESHED') console.log('TOKEN_REFRESHED', session) - }) - ``` - - name: Listen to user updates - js: | - ```js - supabase.auth.onAuthStateChange((event, session) => { - if (event == 'USER_UPDATED') console.log('USER_UPDATED', session) - }) - ``` - - name: Listen to user deleted - js: | - ```js - supabase.auth.onAuthStateChange((event, session) => { - if (event == 'USER_DELETED') console.log('USER_DELETED', session) - }) - ``` - - name: Listen to password recovery events - js: | - ```js - supabase.auth.onAuthStateChange((event, session) => { - if (event == 'PASSWORD_RECOVERY') console.log('PASSWORD_RECOVERY', session) - }) - ``` - auth.api.getUser(): - title: 'getUser()' - $ref: '@supabase/gotrue-js.GoTrueApi.getUser' - notes: | - - Fetches the user object from the database instead of local storage. - - Note that user() fetches the user object from local storage which might not be the most updated. - - Requires the user's access_token. - examples: - - name: Fetch the user object using the access_token jwt. - isSpotlight: true - js: | - ```js - const { user, error } = await supabase.auth.api.getUser( - 'ACCESS_TOKEN_JWT', - ) - ``` - - auth.api.listUsers(): - title: 'listUsers()' - $ref: '@supabase/gotrue-js.GoTrueApi.listUsers' - notes: | - - Requires a `service_role` key. - - This function should be called on a server. Never expose your `service_role` key in the browser. - examples: - - name: Get a full list of users. - isSpotlight: true - js: | - ```js - const { data: user, error } = await supabase.auth.api.listUsers() - ``` - - auth.api.createUser(): - title: 'createUser()' - $ref: '@supabase/gotrue-js.GoTrueApi.createUser' - notes: | - - Requires a `service_role` key. - - This function should be called on a server. Never expose your `service_role` key in the browser. - - If you do not provide the `email_confirm` and `phone_confirm` options to this function, both will default to false. - examples: - - name: Create a new user. - isSpotlight: true - js: | - ```js - const { data: user, error } = await supabase.auth.api.createUser({ - email: 'user@email.com', - password: 'password', - user_metadata: { name: 'Yoda' } - }) - ``` - - name: Auto-confirm email. - isSpotlight: true - js: | - ```js - const { data: user, error } = await supabase.auth.api.createUser({ - email: 'user@email.com', - email_confirm: true - }) - ``` - - name: Auto-confirm phone. - isSpotlight: true - js: | - ```js - const { data: user, error } = await supabase.auth.api.createUser({ - phone: '1234567890', - phone_confirm: true - }) - ``` - - auth.api.deleteUser(): - title: 'deleteUser()' - $ref: '@supabase/gotrue-js.GoTrueApi.deleteUser' - notes: | - - Requires a `service_role` key. - - This function should be called on a server. Never expose your `service_role` key in the browser. - examples: - - name: Remove a user completely. - isSpotlight: true - js: | - ```js - const { data: user, error } = await supabase.auth.api.deleteUser( - '715ed5db-f090-4b8c-a067-640ecee36aa0' - ) - ``` - - auth.api.inviteUserByEmail(): - title: 'inviteUserByEmail()' - $ref: '@supabase/gotrue-js.GoTrueApi.inviteUserByEmail' - notes: | - - Requires a `service_role` key. - - This function should only be called on a server. Never expose your `service_role` key in the browser. - examples: - - name: Basic example. - isSpotlight: false - js: | - ```js - const { data: user, error } = await supabase.auth - .api - .inviteUserByEmail('email@example.com') - ``` - - auth.api.sendMobileOTP(): - title: 'sendMobileOTP()' - $ref: '@supabase/gotrue-js.GoTrueApi.sendMobileOTP' - notes: | - - Requires a `service_role` key. - - This function should only be called on a server. Never expose your `service_role` key in the browser. - examples: - - name: Basic example. - isSpotlight: false - js: | - ```js - const { data: user, error } = await supabase.auth - .api - .sendMobileOTP('12345879') - ``` - - auth.api.resetPasswordForEmail(): - title: 'resetPasswordForEmail()' - $ref: '@supabase/gotrue-js.GoTrueApi.resetPasswordForEmail' - notes: | - Sends a password reset request to an email address. - When the user clicks the reset link in the email they are redirected back to your application. - Prompt the user for a new password and call `auth.update()`: - ```js - const { data, error } = await supabase.auth.update({ - password: new_password, - }) - ``` - examples: - - name: Reset password - isSpotlight: true - js: | - ```js - const { data, error } = await supabase.auth.api.resetPasswordForEmail( - 'user@email.com' - ) - ``` - - name: Reset password (React) - isSpotlight: true - js: | - ```js - /** - * Step 1: Send the user an email to get a password reset token. - * This email contains a link which sends the user back to your application. - */ - const { data, error } = await supabase.auth.api.resetPasswordForEmail( - 'user@email.com' - ) - - /** - * Step 2: Once the user is redirected back to your application, - * ask the user to reset their password. - */ - useEffect(() => { - supabase.auth.onAuthStateChange(async (event, session) => { - if (event == "PASSWORD_RECOVERY") { - const newPassword = prompt("What would you like your new password to be?"); - const { data, error } = await supabase.auth.update({ - password: newPassword, - }) - - if (data) alert("Password updated successfully!") - if (error) alert("There was an error updating your password.") - } - }) - }, []) - ``` - - auth.api.generateLink(): - title: 'generateLink()' - $ref: '@supabase/gotrue-js.GoTrueApi.generateLink' - notes: | - - Requires a `service_role` key. - - This function should only be called on a server. Never expose your `service_role` key in the browser. - examples: - - name: Generate an invite link - isSpotlight: false - js: | - ```js - const { data: user, error } = await supabase.auth.api.generateLink( - 'invite', - 'email@example.com' - ) - ``` - - auth.api.updateUserById(): - title: 'updateUserById()' - $ref: '@supabase/gotrue-js.GoTrueApi.updateUserById' - notes: | - - Requires a `service_role` key. - - This function should only be called on a server. Never expose your `service_role` key in the browser. - examples: - - name: Updates a user's email. - isSpotlight: true - js: | - ```js - const { data: user, error } = await supabase.auth.api.updateUserById( - '6aa5d0d4-2a9f-4483-b6c8-0cf4c6c98ac4', - { email: 'new@email.com' } - ) - ``` - - name: Updates a user's password. - isSpotlight: true - js: | - ```js - const { data: user, error } = await supabase.auth.api.updateUserById( - '6aa5d0d4-2a9f-4483-b6c8-0cf4c6c98ac4', - { password: 'new_password' } - ) - ``` - - name: Updates a user's metadata. - isSpotlight: true - js: | - ```js - const { data: user, error } = await supabase.auth.api.updateUserById( - '6aa5d0d4-2a9f-4483-b6c8-0cf4c6c98ac4', - { user_metadata: { hello: 'world' } } - ) - ``` - - name: Updates a user's app_metadata. - isSpotlight: true - js: | - ```js - const { data: user, error } = await supabase.auth.api.updateUserById( - '6aa5d0d4-2a9f-4483-b6c8-0cf4c6c98ac4', - { app_metadata: { plan: 'trial' } } - ) - ``` - - name: Confirms a user's email address. - isSpotlight: true - js: | - ```js - const { data: user, error } = await supabase.auth.api.updateUserById( - '6aa5d0d4-2a9f-4483-b6c8-0cf4c6c98ac4', - { email_confirm: true } - ) - ``` - - name: Confirms a user's phone number. - isSpotlight: true - js: | - ```js - const { data: user, error } = await supabase.auth.api.updateUserById( - '6aa5d0d4-2a9f-4483-b6c8-0cf4c6c98ac4', - { phone_confirm: true } - ) - ``` - invoke(): - title: 'invoke()' - description: | - Invokes a Supabase Function. - $ref: '@supabase/functions-js.FunctionsClient.invoke' - notes: | - - Requires an Authorization header. - - Invoke params generally match the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) spec. - examples: - - name: Basic invocation. - isSpotlight: true - js: | - ```js - const { data, error } = await supabase.functions.invoke('hello', { - body: JSON.stringify({ foo: 'bar' }) - }) - ``` - - name: Specifying response type. - description: | - By default, `invoke()` will parse the response as JSON. You can parse the response in the following formats: `json`, `blob`, `text`, and `arrayBuffer`. - isSpotlight: true - js: | - ```js - const { data, error } = await supabase.functions.invoke('hello', { - responseType: 'text', - body: JSON.stringify({ foo: 'bar' }) - }) - ``` - - name: Parsing custom headers. - description: | - You can pass custom headers to your function. Note: supabase-js automatically passes the `Authorization` header with the signed in user's JWT. - isSpotlight: true - js: | - ```js - const { data, error } = await supabase.functions.invoke('hello', { - headers: { - "my-custom-header": 'my-custom-header-value' - }, - body: JSON.stringify({ foo: 'bar' }) - }) - ``` - - select(): - title: 'Fetch data: select()' - $ref: '@supabase/postgrest-js."lib/PostgrestQueryBuilder".PostgrestQueryBuilder.select' - notes: | - - By default, Supabase projects will return a maximum of 1,000 rows. This setting can be changed in Project API Settings. It's recommended that you keep it low to limit the payload size of accidental or malicious requests. You can use `range()` queries to paginate through your data. - - `select()` can be combined with [Modifiers](/docs/reference/javascript/using-modifiers) - - `select()` can be combined with [Filters](/docs/reference/javascript/using-filters) - - If using the Supabase hosted platform `apikey` is technically a reserved keyword, since the API gateway will pluck it out for authentication. [It should be avoided as a column name](https://github.com/supabase/supabase/issues/5465). - examples: - - name: Getting your data - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select() - ``` - - name: Selecting specific columns - description: You can select specific fields from your tables. - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name') - ``` - - name: Query foreign tables - description: If your database has foreign key relationships, you can query related tables too. - js: | - ```js - const { data, error } = await supabase - .from('countries') - .select(` - name, - cities ( - name - ) - `) - ``` - note: | - What about join tables - If you're in a situation where your tables are **NOT** directly related, but instead are joined by a _join table_, - you can still use the `select()` method to query the related data. The PostgREST engine detects the relationship automatically. - For more details, [follow the link](https://postgrest.org/en/latest/api.html#embedding-through-join-tables). - - name: Query the same foreign table multiple times - description: | - Sometimes you will need to query the same foreign table twice. - In this case, you can use the name of the joined column to identify - which join you intend to use. For convenience, you can also give an - alias for each column. For example, if we had a shop of products, - and we wanted to get the supplier and the purchaser at the same time - (both in the users) table: - js: | - ```js - const { data, error } = await supabase - .from('products') - .select(` - id, - supplier:supplier_id ( name ), - purchaser:purchaser_id ( name ) - `) - ``` - - name: Filtering with inner joins - description: | - If you want to filter a table based on a child table's values you can use the `!inner()` function. For example, if you wanted - to select all rows in a `message` table which belong to a user with the `username` "Jane": - js: | - ```js - const { data, error } = await supabase - .from('messages') - .select('*, users!inner(*)') - .eq('users.username', 'Jane') - ``` - - name: Querying with count option - description: | - You can get the number of rows by using the count option. - Allowed values for count option are `null`, [exact](https://postgrest.org/en/stable/api.html#exact-count), [planned](https://postgrest.org/en/stable/api.html#planned-count) and [estimated](https://postgrest.org/en/stable/api.html#estimated-count). - js: | - ```js - const { data, error, count } = await supabase - .from('cities') - .select('name', { count: 'exact' }) // if you don't want to return any rows, you can use { count: 'exact', head: true } - ``` - - name: Querying JSON data - description: | - If you have data inside of a JSONB column, you can apply select - and query filters to the data values. Postgres offers a - [number of operators](https://www.postgresql.org/docs/current/functions-json.html) - for querying JSON data. Also see - [PostgREST docs](http://postgrest.org/en/v7.0.0/api.html#json-columns) for more details. - js: | - ```js - const { data, error } = await supabase - .from('users') - .select(` - id, name, - address->street - `) - .eq('address->postcode', 90210) - ``` - - name: Return data as CSV - description: | - By default the data is returned in JSON format, however you can also request for it to be returned as Comma Separated Values. - js: | - ```js - const { data, error } = await supabase - .from('users') - .select() - .csv() - ``` - - name: Aborting requests in-flight - description: | - You can use an [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to abort requests. Note that `status` and `statusText` doesn't mean anything for aborted requests, since the request wasn't actually fulfilled. - js: | - ```js - const ac = new AbortController() - supabase - .from('very_big_table') - .select() - .abortSignal(ac.signal) - .then(console.log) - ac.abort() - // { - // error: { - // message: 'FetchError: The user aborted a request.', - // details: '', - // hint: '', - // code: '' - // }, - // data: null, - // body: null, - // count: null, - // status: 400, - // statusText: 'Bad Request' - // } - ``` - - insert(): - title: 'Create data: insert()' - $ref: '@supabase/postgrest-js."lib/PostgrestQueryBuilder".PostgrestQueryBuilder.insert' - notes: | - - By default, every time you run `insert()`, the client library will make a `select` to return the full record. - This is convenient, but it can also cause problems if your Policies are not configured to allow the `select` operation. - If you are using Row Level Security and you are encountering problems, try setting the `returning` param to `minimal`. - examples: - - name: Create a record - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .insert([ - { name: 'The Shire', country_id: 554 } - ]) - ``` - - name: Bulk create - description: | - When running a bulk create, the operation is handled in a single transaction. If any of the inserts fail, all other operations are - rolled back. - js: | - ```js - const { data, error } = await supabase - .from('cities') - .insert([ - { name: 'The Shire', country_id: 554 }, - { name: 'Rohan', country_id: 555 }, - ]) - ``` - - name: Upsert - description: | - For upsert, if set to true, primary key columns would need to be included - in the data parameter in order for an update to properly happen. Also, primary keys - used must be natural, not surrogate. There are however, - [workarounds](https://github.com/PostgREST/postgrest/issues/1118) - for surrogate primary keys. - js: | - ```js - const { data, error } = await supabase - .from('cities') - .insert( - [ - { name: 'The Shire', country_id: 554 }, - { name: 'Rohan', country_id: 555 }, - { name: 'City by the Bay', country_id:840} - ], - { upsert: true }) - ``` - - update(): - title: 'Modify data: update()' - $ref: '@supabase/postgrest-js."lib/PostgrestQueryBuilder".PostgrestQueryBuilder.update' - notes: | - - `update()` should always be combined with [Filters](/docs/reference/javascript/using-filters) to target the item(s) you wish to update. - examples: - - name: Updating your data - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .update({ name: 'Middle Earth' }) - .match({ name: 'Auckland' }) - ``` - - name: Updating JSON data - description: | - Postgres offers a - [number of operators](https://www.postgresql.org/docs/current/functions-json.html) - for working with JSON data. Right now it is only possible to update an entire JSON document, - but we are [working on ideas](https://github.com/PostgREST/postgrest/issues/465) for updating individual keys. - js: | - ```js - const { data, error } = await supabase - .from('users') - .update(` - address: { - street: 'Melrose Place', - postcode: 90210 - } - `) - .eq('address->postcode', 90210) - ``` - - upsert(): - title: 'Upsert data: upsert()' - $ref: '@supabase/postgrest-js."lib/PostgrestQueryBuilder".PostgrestQueryBuilder.upsert' - notes: | - - Primary keys should be included in the data payload in order for an update to work correctly. - - Primary keys must be natural, not surrogate. There are however, [workarounds](https://github.com/PostgREST/postgrest/issues/1118) for surrogate primary keys. - - If you need to insert new data and update existing data at the same time, use [Postgres triggers](https://github.com/supabase/postgrest-js/issues/173#issuecomment-825124550). - examples: - - name: Upsert your data - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('messages') - .upsert({ id: 3, message: 'foo', username: 'supabot' }) - ``` - - name: Bulk Upsert your data - isSpotlight: false - js: | - ```js - const { data, error } = await supabase - .from('messages') - .upsert([ - { id: 3, message: 'foo', username: 'supabot' }, - { id: 4, message: 'bar', username: 'supabot' } - ]) - ``` - - name: Upserting into tables with constraints - description: | - Running the following will cause supabase to upsert data into the `users` table. - If the username 'supabot' already exists, the `onConflict` argument tells supabase to overwrite that row - based on the column passed into `onConflict`. - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('users') - .upsert({ username: 'supabot' }, { onConflict: 'username' }) - ``` - - name: Return the exact number of rows - isSpotlight: true - js: | - ```js - const { data, error, count } = await supabase - .from('users') - .upsert({ - id: 3, message: 'foo', - username: 'supabot' - }, { - count: 'exact' - }) - ``` - - delete(): - title: 'Delete data: delete()' - $ref: '@supabase/postgrest-js."lib/PostgrestQueryBuilder".PostgrestQueryBuilder.delete' - notes: | - - `delete()` should always be combined with [filters](/docs/reference/javascript/using-filters) to target the item(s) you wish to delete. - - If you use `delete()` with filters and you have - [RLS](/docs/learn/auth-deep-dive/auth-row-level-security) enabled, only - rows visible through `SELECT` policies are deleted. Note that by default - no rows are visible, so you need at least one `SELECT`/`ALL` policy that - makes the rows visible. - examples: - - name: Delete records - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .delete() - .match({ id: 666 }) - ``` - - rpc(): - title: 'Postgres functions: rpc()' - description: | - You can call Postgres functions as _Remote Procedure Calls_, logic in your database that you can execute from anywhere. - Functions are useful when the logic rarely changes—like for password resets and updates. - - ```sql - create or replace function hello_world() returns text as $$ - select 'Hello world'; - $$ language sql; - ``` - $ref: '@supabase/postgrest-js."lib/PostgrestRpcBuilder".PostgrestRpcBuilder.rpc' - examples: - - name: Call a Postgres function without arguments - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .rpc('hello_world') - ``` - - name: Call a Postgres function with arguments - js: | - ```js - const { data, error } = await supabase - .rpc('echo_city', { name: 'The Shire' }) - ``` - - name: Bulk processing - description: You can process large payloads at once using [array parameters](https://postgrest.org/en/stable/api.html#calling-functions-with-array-parameters). - js: | - ```js - const { data, error } = await postgrest - .rpc('echo_cities', { names: ['The Shire', 'Mordor'] }) - ``` - - name: Call a Postgres function with filters - description: | - Postgres functions that return tables can also be combined with - [Modifiers](/docs/reference/javascript/using-modifiers) and - [Filters](/docs/reference/javascript/using-filters). - js: | - ```js - const { data, error } = await supabase - .rpc('echo_all_cities') - .select('name, population') - .eq('name', 'The Shire') - ``` - - name: Call a Postgres function with a count option - description: | - You can specify a count option to get the row count along with your data. - Allowed values for count option are `null`, `exact`, `planned` and `estimated`. - js: | - ```js - const { data, error, count } = await supabase - .rpc('hello_world', {}, { count: 'exact' }) - ``` - - subscribe(): - title: 'on().subscribe()' - $ref: '@supabase/supabase-js.lib/SupabaseQueryBuilder.SupabaseQueryBuilder.on' - notes: | - - Realtime is disabled by default for new Projects for better database performance and security. You can turn it on by [managing replication](/docs/guides/api#managing-realtime). - - If you want to receive the "previous" data for updates and deletes, you will need to set `REPLICA IDENTITY` to `FULL`, like this: `ALTER TABLE your_table REPLICA IDENTITY FULL;` - examples: - - name: Listen to all database changes - isSpotlight: true - js: | - ```js - const mySubscription = supabase - .from('*') - .on('*', payload => { - console.log('Change received!', payload) - }) - .subscribe() - ``` - - name: Listening to a specific table - js: | - ```js - const mySubscription = supabase - .from('countries') - .on('*', payload => { - console.log('Change received!', payload) - }) - .subscribe() - ``` - - name: Listening to inserts - js: | - ```js - const mySubscription = supabase - .from('countries') - .on('INSERT', payload => { - console.log('Change received!', payload) - }) - .subscribe() - ``` - - name: Listening to updates - description: | - By default, Supabase will send only the updated record. If you want to receive the previous values as well you can - enable full replication for the table you are listening too: - - ```sql - alter table "your_table" replica identity full; - ``` - js: | - ```js - const mySubscription = supabase - .from('countries') - .on('UPDATE', payload => { - console.log('Change received!', payload) - }) - .subscribe() - ``` - - name: Listening to deletes - description: | - By default, Supabase does not send deleted records. If you want to receive the deleted record you can - enable full replication for the table you are listening too: - - ```sql - alter table "your_table" replica identity full; - ``` - js: | - ```js - const mySubscription = supabase - .from('countries') - .on('DELETE', payload => { - console.log('Change received!', payload) - }) - .subscribe() - ``` - - name: Listening to multiple events - description: You can chain listeners if you want to listen to multiple events for each table. - js: | - ```js - const mySubscription = supabase - .from('countries') - .on('INSERT', handleRecordInserted) - .on('DELETE', handleRecordDeleted) - .subscribe() - ``` - - name: Listening to row level changes - description: You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match. - notes: | - - ``eq`` filter works with all database types as under the hood, it's casting both the filter value and the database value to the correct type and then comparing them. - js: | - ```js - const mySubscription = supabase - .from('countries:id=eq.200') - .on('UPDATE', handleRecordUpdated) - .subscribe() - ``` - - removeSubscription(): - title: 'removeSubscription()' - $ref: '@supabase/supabase-js.index.SupabaseClient.removeSubscription' - notes: | - - Removing subscriptions is a great way to maintain the performance of your project's database. Supabase will automatically handle cleanup 30 seconds after a user is disconnected, but unused subscriptions may cause degradation as more users are simultaneously subscribed. - examples: - - name: Remove a subscription - isSpotlight: true - js: | - ```js - supabase.removeSubscription(mySubscription) - ``` - - removeAllSubscriptions(): - title: 'removeAllSubscriptions()' - $ref: '@supabase/supabase-js.index.SupabaseClient.removeAllSubscriptions' - notes: | - - Removing subscriptions is a great way to maintain the performance of your project's database. Supabase will automatically handle cleanup 30 seconds after a user is disconnected, but unused subscriptions may cause degradation as more users are simultaneously subscribed. - examples: - - name: Removes all subscriptions - isSpotlight: true - js: | - ```js - supabase.removeAllSubscriptions() - ``` - - getSubscriptions(): - title: 'getSubscriptions()' - $ref: '@supabase/supabase-js.index.SupabaseClient.getSubscriptions' - examples: - - name: Get all subscriptions - isSpotlight: true - js: | - ```js - const subscriptions = supabase.getSubscriptions() - ``` - - storage.listBuckets(): - title: 'listBuckets()' - $ref: '@supabase/storage-js."lib/StorageBucketApi".StorageBucketApi.listBuckets' - notes: | - - Policy permissions required: - - `buckets` permissions: `select` - - `objects` permissions: none - examples: - - name: List buckets - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .listBuckets() - ``` - - storage.getBucket(): - title: 'getBucket()' - $ref: '@supabase/storage-js."lib/StorageBucketApi".StorageBucketApi.getBucket' - notes: | - - Policy permissions required: - - `buckets` permissions: `select` - - `objects` permissions: none - examples: - - name: Get bucket - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .getBucket('avatars') - ``` - - storage.createBucket(): - title: 'createBucket()' - $ref: '@supabase/storage-js."lib/StorageBucketApi".StorageBucketApi.createBucket' - notes: | - - Policy permissions required: - - `buckets` permissions: `insert` - - `objects` permissions: none - examples: - - name: Create bucket - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .createBucket('avatars', { public: false }) - ``` - - storage.emptyBucket(): - title: 'emptyBucket()' - $ref: '@supabase/storage-js."lib/StorageBucketApi".StorageBucketApi.emptyBucket' - notes: | - - Policy permissions required: - - `buckets` permissions: `select` - - `objects` permissions: `select` and `delete` - examples: - - name: Empty bucket - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .emptyBucket('avatars') - ``` - storage.updateBucket(): - title: 'updateBucket()' - $ref: '@supabase/storage-js."lib/StorageBucketApi".StorageBucketApi.updateBucket' - notes: | - - Policy permissions required: - - `buckets` permissions: `update` - - `objects` permissions: none - examples: - - name: Update bucket - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .updateBucket('avatars', { public: false }) - ``` - - storage.deleteBucket(): - title: 'deleteBucket()' - $ref: '@supabase/storage-js."lib/StorageBucketApi".StorageBucketApi.deleteBucket' - notes: | - - Policy permissions required: - - `buckets` permissions: `select` and `delete` - - `objects` permissions: none - examples: - - name: Delete bucket - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .deleteBucket('avatars') - ``` - - storage.from.upload(): - title: 'from.upload()' - $ref: '@supabase/storage-js."lib/StorageFileApi".StorageFileApi.upload' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `insert` - - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Upload file using `ArrayBuffer` from base64 file data instead, see example below. - examples: - - name: Upload file - isSpotlight: true - js: | - ```js - const avatarFile = event.target.files[0] - const { data, error } = await supabase - .storage - .from('avatars') - .upload('public/avatar1.png', avatarFile, { - cacheControl: '3600', - upsert: false - }) - ``` - - name: Upload file using `ArrayBuffer` from base64 file data - js: | - ```js - import { decode } from 'base64-arraybuffer' - - const { data, error } = await supabase - .storage - .from('avatars') - .upload('public/avatar1.png', decode('base64FileData'), { - contentType: 'image/png' - }) - ``` - - storage.from.update(): - title: 'from.update()' - $ref: '@supabase/storage-js."lib/StorageFileApi".StorageFileApi.update' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `update` and `select` - - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Update file using `ArrayBuffer` from base64 file data instead, see example below. - examples: - - name: Update file - isSpotlight: true - js: | - ```js - const avatarFile = event.target.files[0] - const { data, error } = await supabase - .storage - .from('avatars') - .update('public/avatar1.png', avatarFile, { - cacheControl: '3600', - upsert: false - }) - ``` - - name: Update file using `ArrayBuffer` from base64 file data - js: | - ```js - import {decode} from 'base64-arraybuffer' - - const { data, error } = await supabase - .storage - .from('avatars') - .update('public/avatar1.png', decode('base64FileData'), { - contentType: 'image/png' - }) - ``` - - storage.from.move(): - title: 'from.move()' - $ref: '@supabase/storage-js."lib/StorageFileApi".StorageFileApi.move' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `update` and `select` - examples: - - name: Move file - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .from('avatars') - .move('public/avatar1.png', 'private/avatar2.png') - ``` - - storage.from.copy(): - title: 'from.copy()' - $ref: '@supabase/storage-js."lib/StorageFileApi".StorageFileApi.copy' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `insert` and `select` - examples: - - name: Copy file - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .from('avatars') - .copy('public/avatar1.png', 'private/avatar2.png') - ``` - - storage.from.createSignedUrl(): - title: 'from.createSignedUrl()' - $ref: '@supabase/storage-js."lib/StorageFileApi".StorageFileApi.createSignedUrl' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `select` - examples: - - name: Create Signed URL - isSpotlight: true - js: | - ```js - const { signedURL, error } = await supabase - .storage - .from('avatars') - .createSignedUrl('folder/avatar1.png', 60) - ``` - - storage.from.createSignedUrls(): - title: 'from.createSignedUrls()' - $ref: '@supabase/storage-js."lib/StorageFileApi".StorageFileApi.createSignedUrls' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `select` - examples: - - name: Create Signed URLs - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .from('avatars') - .createSignedUrls(['folder/avatar1.png', 'folder/avatar2.png'], 60) - ``` - - storage.from.getPublicUrl(): - title: 'from.getPublicUrl()' - $ref: '@supabase/storage-js."lib/StorageFileApi".StorageFileApi.getPublicUrl' - notes: | - - The bucket needs to be set to public, either via [updateBucket()](/docs/reference/javascript/storage-updatebucket) or by going to Storage on [app.supabase.com](https://app.supabase.com), clicking the overflow menu on a bucket and choosing "Make public" - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: none - examples: - - name: Returns the URL for an asset in a public bucket - isSpotlight: true - js: | - ```js - const { publicURL, error } = supabase - .storage - .from('public-bucket') - .getPublicUrl('folder/avatar1.png') - ``` - - storage.from.download(): - title: 'from.download()' - $ref: '@supabase/storage-js."lib/StorageFileApi".StorageFileApi.download' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `select` - examples: - - name: Download file - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .from('avatars') - .download('folder/avatar1.png') - ``` - - storage.from.remove(): - title: 'from.remove()' - $ref: '@supabase/storage-js."lib/StorageFileApi".StorageFileApi.remove' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `delete` and `select` - examples: - - name: Delete file - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .from('avatars') - .remove(['folder/avatar1.png']) - ``` - - storage.from.list(): - title: 'from.list()' - $ref: '@supabase/storage-js."lib/StorageFileApi".StorageFileApi.list' - notes: | - - Policy permissions required: - - `buckets` permissions: none - - `objects` permissions: `select` - examples: - - name: List files in a bucket - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .from('avatars') - .list('folder', { - limit: 100, - offset: 0, - sortBy: { column: 'name', order: 'asc' }, - }) - ``` - - name: Search files in a bucket - js: | - ```js - const { data, error } = await supabase - .storage - .from('avatars') - .list('folder', { - limit: 100, - offset: 0, - sortBy: { column: 'name', order: 'asc' }, - search: 'jon' - }) - ``` - - Using Modifiers: - description: | - Modifiers can be used on `select()` queries. - - If a Postgres function returns a table response, you can also apply modifiers to the `rpc()` function. - - limit(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.limit' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .limit(1) - ``` - - name: With embedded resources - js: | - ```js - const { data, error } = await supabase - .from('countries') - .select('name, cities(name)') - .eq('name', 'United States') - .limit(1, { foreignTable: 'cities' }) - ``` - - order(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.order' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .order('id', { ascending: false }) - ``` - - name: With embedded resources - js: | - ```js - const { data, error } = await supabase - .from('countries') - .select('name, cities(name)') - .eq('name', 'United States') - .order('name', {foreignTable: 'cities'}) - ``` - - name: Ordering multiple columns - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name', 'country_id') - .order('country_id', { ascending: false }) - .order('name', { ascending: false }) - ``` - range(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.range' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .range(0,3) - ``` - - single(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.single' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .limit(1) - .single() - ``` - - maybeSingle(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.maybeSingle' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .eq('name', 'Singapore') - .maybeSingle() - ``` - - Using Filters: - description: | - Filters can be used on `select()`, `update()`, and `delete()` queries. - - If a Postgres function returns a table response, you can also apply filters. - - ### Applying Filters - - You must apply your filters to the end of your query. For example: - - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .eq('name', 'The Shire') // Correct - - const { data, error } = await supabase - .from('cities') - .eq('name', 'The Shire') // Incorrect - .select('name, country_id') - ``` - - ### Chaining - - Filters can be chained together to produce advanced queries. For example: - - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .gte('population', 1000) - .lt('population', 10000) - ``` - - ### Conditional Chaining - - Filters can be built up one step at a time and then executed. For example: - - ```js - const filterByName = null - const filterPopLow = 1000 - const filterPopHigh = 10000 - - let query = supabase - .from('cities') - .select('name, country_id') - - if (filterByName) { query = query.eq('name', filterByName) } - if (filterPopLow) { query = query.gte('population', filterPopLow) } - if (filterPopHigh) { query = query.lt('population', filterPopHigh) } - - const { data, error } = await query - ``` - - .or(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.or' - notes: | - - `.or()` expects you to use the raw [PostgREST syntax](https://postgrest.org/en/stable/api.html#horizontal-filtering-rows) for the filter names and values. - - ```js - .or('id.in.(6,7), arraycol.cs.{"a","b"}') // Use Postgres list () for in filter. Array {} for array column and 'cs' for contains. - .or(`id.in.(${arrList}),arraycol.cs.{${arr}}`) // You can insert a javascipt array for list or array on array column. - .or(`id.in.(${arrList}),rangecol.cs.[${arrRange})`) // You can insert a javascipt array for list or range on a range column. - ``` - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .or('id.eq.20,id.eq.30') - ``` - - name: Use `or` with `and` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .or('id.gt.20,and(name.eq.New Zealand,name.eq.France)') - ``` - - name: Use `or` on foreign tables - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('countries') - .select('id, cities(*)') - .or('name.eq.Wellington,name.eq.Paris', { foreignTable: "cities" }) - ``` - - .not(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.not' - notes: | - - `.not()` expects you to use the raw [PostgREST syntax](https://postgrest.org/en/stable/api.html#horizontal-filtering-rows) for the filter names and values. - - ```js - .not('name','eq','Paris') - .not('arraycol','cs','{"a","b"}') // Use Postgres array {} for array column and 'cs' for contains. - .not('rangecol','cs','(1,2]') // Use Postgres range syntax for range column. - .not('id','in','(6,7)') // Use Postgres list () for in filter. - .not('id','in',`(${arr})`) // You can insert a javascript array. - ``` - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .not('name', 'eq', 'Paris') - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .update({ name: 'Mordor' }) - .not('name', 'eq', 'Paris') - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .delete() - .not('name', 'eq', 'Paris') - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_cities') - .not('name', 'eq', 'Paris') - ``` - - .match(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.match' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .match({name: 'Beijing', country_id: 156}) - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .update({ name: 'Mordor' }) - .match({name: 'Beijing', country_id: 156}) - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .delete() - .match({name: 'Beijing', country_id: 156}) - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_cities') - .match({name: 'Beijing', country_id: 156}) - ``` - - .eq(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.eq' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .eq('name', 'The shire') - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .update({ name: 'Mordor' }) - .eq('name', 'San Francisco') - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .delete() - .eq('name', 'Mordor') - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_cities') - .eq('name', 'San Francisco') - ``` - - .neq(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.neq' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .neq('name', 'The shire') - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .update({ name: 'Mordor' }) - .neq('name', 'San Francisco') - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .delete() - .neq('name', 'Mordor') - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_cities') - .neq('name', 'Lagos') - ``` - - .gt(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.gt' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .gt('country_id', 250) - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .update({ name: 'Mordor' }) - .gt('country_id', 250) - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .delete() - .gt('country_id', 250) - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_cities') - .gt('country_id', 250) - ``` - - .gte(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.gte' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .gte('country_id', 250) - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .update({ name: 'Mordor' }) - .gte('country_id', 250) - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .delete() - .gte('country_id', 250) - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_cities') - .gte('country_id', 250) - ``` - - .lt(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.lt' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .lt('country_id', 250) - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .update({ name: 'Mordor' }) - .lt('country_id', 250) - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .delete() - .lt('country_id', 250) - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_cities') - .lt('country_id', 250) - ``` - - .lte(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.lte' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .lte('country_id', 250) - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .update({ name: 'Mordor' }) - .lte('country_id', 250) - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .delete() - .lte('country_id', 250) - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_cities') - .lte('country_id', 250) - ``` - - .like(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.like' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .like('name', '%la%') - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .update({ name: 'Mordor' }) - .like('name', '%la%') - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .delete() - .like('name', '%la%') - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_cities') - .like('name', '%la%') - ``` - - .ilike(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.ilike' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .ilike('name', '%la%') - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .update({ name: 'Mordor' }) - .ilike('name', '%la%') - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .delete() - .ilike('name', '%la%') - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_cities') - .ilike('name', '%la%') - ``` - - .is(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.is' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .is('name', null) - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .update({ name: 'Mordor' }) - .is('name', null) - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .delete() - .is('name', null) - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_cities') - .is('name', null) - ``` - - .in(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.in' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .in('name', ['Rio de Janeiro', 'San Francisco']) - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .update({ name: 'Mordor' }) - .in('name', ['Rio de Janeiro', 'San Francisco']) - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .delete() - .in('name', ['Rio de Janeiro', 'San Francisco']) - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_cities') - .in('name', ['Rio de Janeiro', 'San Francisco']) - ``` - - .contains(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.contains' - notes: | - - `.contains()` can work on array columns or range columns. - It is very useful for finding rows where a tag array contains all the values in the filter array. - - ```js - .contains('arraycol',["a","b"]) // You can use a javascript array for an array column - .contains('arraycol','{"a","b"}') // You can use a string with Postgres array {} for array column. - .contains('rangecol','(1,2]') // Use Postgres range syntax for range column. - .contains('rangecol',`(${arr}]`) // You can insert an array into a string. - ``` - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('countries') - .select('name, id, main_exports') - .contains('main_exports', ['oil']) - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('countries') - .update({ name: 'Mordor' }) - .contains('main_exports', ['oil']) - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('countries') - .delete() - .contains('main_exports', ['oil']) - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_countries') - .contains('main_exports', ['oil']) - ``` - - .containedBy(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.containedBy' - notes: | - - `.containedBy()` can work on array columns or range columns. - - ```js - .containedBy('arraycol',["a","b"]) // You can use a javascript array for an array column - .containedBy('arraycol','{"a","b"}') // You can use a string with Postgres array {} for array column. - .containedBy('rangecol','(1,2]') // Use Postgres range syntax for range column. - .containedBy('rangecol',`(${arr}]`) // You can insert an array into a string. - ``` - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('countries') - .select('name, id, main_exports') - .containedBy('main_exports', ['cars', 'food', 'machine']) - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('countries') - .update({ name: 'Mordor' }) - .containedBy('main_exports', ['orks', 'surveillance', 'evil']) - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('countries') - .delete() - .containedBy('main_exports', ['cars', 'food', 'machine']) - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_countries') - .containedBy('main_exports', ['cars', 'food', 'machine']) - ``` - - .rangeLt(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.rangeLt' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('countries') - .select('name, id, population_range_millions') - .rangeLt('population_range_millions', '[150, 250]') - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('countries') - .update({ name: 'Mordor' }) - .rangeLt('population_range_millions', '[150, 250]') - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('countries') - .delete() - .rangeLt('population_range_millions', '[150, 250]') - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_countries') - .rangeLt('population_range_millions', '[150, 250]') - ``` - - .rangeGt(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.rangeGt' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('countries') - .select('name, id, population_range_millions') - .rangeGt('population_range_millions', '[150, 250]') - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('countries') - .update({ name: 'Mordor' }) - .rangeGt('population_range_millions', '[150, 250]') - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('countries') - .delete() - .rangeGt('population_range_millions', '[150, 250]') - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_countries') - .rangeGt('population_range_millions', '[150, 250]') - ``` - - .rangeGte(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.rangeGte' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('countries') - .select('name, id, population_range_millions') - .rangeGte('population_range_millions', '[150, 250]') - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('countries') - .update({ name: 'Mordor' }) - .rangeGte('population_range_millions', '[150, 250]') - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('countries') - .delete() - .rangeGte('population_range_millions', '[150, 250]') - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_countries') - .rangeGte('population_range_millions', '[150, 250]') - ``` - - .rangeLte(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.rangeLte' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('countries') - .select('name, id, population_range_millions') - .rangeLte('population_range_millions', '[150, 250]') - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('countries') - .update({ name: 'Mordor' }) - .rangeLte('population_range_millions', '[150, 250]') - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('countries') - .delete() - .rangeLte('population_range_millions', '[150, 250]') - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_countries') - .rangeLte('population_range_millions', '[150, 250]') - ``` - - .rangeAdjacent(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.rangeAdjacent' - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('countries') - .select('name, id, population_range_millions') - .rangeAdjacent('population_range_millions', '[70, 185]') - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('countries') - .update({ name: 'Mordor' }) - .rangeAdjacent('population_range_millions', '[70, 185]') - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('countries') - .delete() - .rangeAdjacent('population_range_millions', '[70, 185]') - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_countries') - .rangeAdjacent('population_range_millions', '[70, 185]') - ``` - - .overlaps(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.overlaps' - notes: | - - `.overlaps()` can work on array columns or range columns. - - ```js - .overlaps('arraycol',["a","b"]) // You can use a javascript array for an array column - .overlaps('arraycol','{"a","b"}') // You can use a string with Postgres array {} for array column. - .overlaps('rangecol','(1,2]') // Use Postgres range syntax for range column. - .overlaps('rangecol',`(${arr}]`) // You can insert an array into a string. - ``` - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('countries') - .select('name, id, main_exports') - .overlaps('main_exports', ['computers', 'minerals']) - ``` - - name: With `update()` - js: | - ```js - let countries = await supabase - .from('countries') - .update({ name: 'Mordor' }) - .overlaps('main_exports', ['computers', 'minerals']) - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('countries') - .delete() - .overlaps('main_exports', ['computers', 'minerals']) - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_countries') - .overlaps('main_exports', ['computers', 'minerals']) - ``` - - .textSearch(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.textSearch' - examples: - - name: Text search - js: | - ```js - const { data, error } = await supabase - .from('quotes') - .select('catchphrase') - .textSearch('catchphrase', `'fat' & 'cat'`, { - config: 'english' - }) - ``` - - name: Basic normalization - description: Uses PostgreSQL's `plainto_tsquery` function. - js: | - ```js - const { data, error } = await supabase - .from('quotes') - .select('catchphrase') - .textSearch('catchphrase', `'fat' & 'cat'`, { - type: 'plain', - config: 'english' - }) - ``` - - name: Full normalization - description: Uses PostgreSQL's `phraseto_tsquery` function. - js: | - ```js - const { data, error } = await supabase - .from('quotes') - .select('catchphrase') - .textSearch('catchphrase', `'fat' & 'cat'`, { - type: 'phrase', - config: 'english' - }) - ``` - - name: Websearch - description: | - Uses PostgreSQL's `websearch_to_tsquery` function. - This function will never raise syntax errors, which makes it possible to use raw user-supplied input for search, and can be used - with advanced operators. - - - `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery. - - `"quoted text"`: text inside quote marks will be converted to terms separated by <-> operators, as if processed by phraseto_tsquery. - - `OR`: the word “or” will be converted to the | operator. - - `-`: a dash will be converted to the ! operator. - - js: | - ```js - const { data, error } = await supabase - .from('quotes') - .select('catchphrase') - .textSearch('catchphrase', `'fat or cat'`, { - type: 'websearch', - config: 'english' - }) - ``` - - .filter(): - $ref: '@supabase/postgrest-js."lib/PostgrestFilterBuilder".PostgrestFilterBuilder.filter' - notes: | - - `.filter()` expects you to use the raw [PostgREST syntax](https://postgrest.org/en/stable/api.html#horizontal-filtering-rows) for the filter names and values, so it should only be used as an escape hatch in case other filters don't work. - ```js - .filter('arraycol','cs','{"a","b"}') // Use Postgres array {} for array column and 'cs' for contains. - .filter('rangecol','cs','(1,2]') // Use Postgres range syntax for range column. - .filter('id','in','(6,7)') // Use Postgres list () for in filter. - .filter('id','in',`(${arr})`) // You can insert a javascript array. - ``` - examples: - - name: With `select()` - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .filter('name', 'in', '("Paris","Tokyo")') - ``` - - name: With `update()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .update({ name: 'Mordor' }) - .filter('name', 'in', '("Paris","Tokyo")') - ``` - - name: With `delete()` - js: | - ```js - const { data, error } = await supabase - .from('cities') - .delete() - .filter('name', 'in', '("Paris","Tokyo")') - ``` - - name: With `rpc()` - js: | - ```js - // Only valid if the Postgres function returns a table type. - const { data, error } = await supabase - .rpc('echo_all_cities') - .filter('name', 'in', '("Paris","Tokyo")') - ``` - - name: Filter embedded resources - js: | - ```js - const { data, error } = await supabase - .from('cities') - .select('name, countries ( name )') - .filter('countries.name', 'in', '("France","Japan")') - ``` diff --git a/spec/supabase_js_v2--old.yml b/spec/supabase_js_v2--old.yml deleted file mode 100644 index f245467e26c..00000000000 --- a/spec/supabase_js_v2--old.yml +++ /dev/null @@ -1,5390 +0,0 @@ -openref: 0.1 - -info: - id: reference/supabase-js - title: Supabase Client - description: | - - Supabase JavaScript. - - definition: ../../spec/enrichments/tsdoc_v2/combined.json - specUrl: https://github.com/supabase/supabase/edit/master/spec/supabase_js_v2.yml - slugPrefix: '/' - libraries: - - name: 'JavaScript' - id: 'js' - version: '0.0.1' - -pages: - Initializing: - $ref: '@supabase/supabase-js.index.SupabaseClient.constructor' - description: | - You can initialize a new Supabase client using the `createClient()` method. - - The Supabase client is your entrypoint to the rest of the Supabase functionality - and is the easiest way to interact with everything we offer within the Supabase ecosystem. - - examples: - - name: createClient() - js: | - ```js - import { createClient } from '@supabase/supabase-js' - - // Create a single supabase client for interacting with your database - const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key') - ``` - - name: With additional parameters - js: | - ```js - import { createClient } from '@supabase/supabase-js' - - const options = { - db: { - schema: 'public', - }, - auth: { - autoRefreshToken: true, - persistSession: true, - detectSessionInUrl: true - }, - global: { - headers: { 'x-my-custom-header': 'my-app-name' }, - }, - } - const supabase = createClient("https://xyzcompany.supabase.co", "public-anon-key", options) - ``` - - name: API schemas - js: | - ```js - import { createClient } from '@supabase/supabase-js' - - // Provide a custom schema. Defaults to "public". - const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key', { - db: { schema: 'other_schema' } - }) - ``` - - By default the API server points to the `public` schema. You can enable other database schemas within the Dashboard. - Go to `Settings > API > Schema` and add the schema which you want to expose to the API. - - Note: each client connection can only access a single schema, so the code above can access the `other_schema` schema but cannot access the `public` schema. - - name: Custom `fetch` implementation - js: | - ```js - import { createClient } from '@supabase/supabase-js' - - const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key', { - global: { fetch: fetch.bind(globalThis) } - }) - ``` - - `supabase-js` uses the [`cross-fetch`](https://www.npmjs.com/package/cross-fetch) library to make HTTP requests, - but an alternative `fetch` implementation can be provided as an option. - This is most useful in environments where `cross-fetch` is not compatible (for instance Cloudflare Workers). - - name: React Native options - js: | - ```js - import { createClient } from '@supabase/supabase-js' - import AsyncStorage from "@react-native-async-storage/async-storage"; - - const supabase = createClient("https://xyzcompany.supabase.co", "public-anon-key", { - auth: { - storage: AsyncStorage, - autoRefreshToken: true, - persistSession: true, - detectSessionInUrl: false, - }, - }); - ``` - - For React Native we recommend using `AsyncStorage` as the storage implementation for Supabase Auth. - auth.signUp(): - title: 'signUp()' - $ref: '@supabase/gotrue-js.GoTrueClient.signUp' - notes: | - - By default, the user needs to verify their email address before logging in. To turn this off, disable **Confirm email** in [your project](https://app.supabase.com/project/_/auth/settings). - - **Confirm email** determines if users need to confirm their email address after signing up. - - If **Confirm email** is enabled, a `user` is returned but `session` is null. - - If **Confirm email** is disabled, both a `user` and a `session` are returned. - - When the user confirms their email address, they are redirected to the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url) by default. You can modify your `SITE_URL` or add additional redirect URLs in [your project](https://app.supabase.com/project/_/auth/settings). - - If signUp() is called for an existing confirmed user: - - If **Confirm email** is enabled in [your project](https://app.supabase.com/project/_/auth/settings), an obfuscated/fake user object is returned. - - If **Confirm email** is disabled, the error message, `User already registered` is returned. - - To fetch the currently logged-in user, refer to [`getUser()`](/docs/reference/javascript/auth-getuser). - examples: - - name: Sign up. - isSpotlight: true - js: | - ```js - const { data, error } = await supabase.auth.signUp({ - email: 'example@email.com', - password: 'example-password', - }) - ``` - - name: Sign up with additional user metadata. - isSpotlight: false - js: | - ```js - const { data, error } = await supabase.auth.signUp( - { - email: 'example@email.com', - password: 'example-password', - options: { - data: { - first_name: 'John', - age: 27, - } - } - } - ) - ``` - auth.signInWithPassword(): - title: 'signInWithPassword()' - $ref: '@supabase/gotrue-js.GoTrueClient.signInWithPassword' - notes: | - - Requires either an email and password or a phone number and password. - examples: - - name: Sign in with email and password - isSpotlight: true - js: | - ```js - const { data, error } = await supabase.auth.signInWithPassword({ - email: 'example@email.com', - password: 'example-password', - }) - ``` - - name: Sign in with phone and password - isSpotlight: false - js: | - ```js - const { data, error } = await supabase.auth.signInWithPassword({ - phone: '+13334445555', - password: 'some-password', - }) - - // After receiving a SMS with a OTP. - const { data, error } = await supabase.auth.verifyOtp({ - phone: '+13334445555', - token: '123456', - }) - ``` - auth.signInWithOtp(): - title: 'signInWithOtp()' - $ref: '@supabase/gotrue-js.GoTrueClient.signInWithOtp' - notes: | - - Requires either an email or phone number. - - This method is used for passwordless sign-ins where a OTP is sent to the user's email or phone number. - - If you're using an email, you can configure whether you want the user to receive a magiclink or a OTP. - - If you're using phone, you can configure whether you want the user to receive a OTP. - - The magic link's destination URL is determined by the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url). You can modify the `SITE_URL` or add additional redirect urls in [your project](https://app.supabase.com/project/_/auth/settings). - examples: - - name: Sign in with email. - isSpotlight: true - description: The user will be sent an email which contains either a magiclink or a OTP or both. By default, a given user can only request a OTP once every 60 seconds. - js: | - ```js - const { data, error } = await supabase.auth.signInWithOtp({ - email: 'example@email.com', - }) - ``` - - name: Sign in with SMS OTP. - isSpotlight: false - description: The user will be sent a SMS which contains a OTP. By default, a given user can only request a OTP once every 60 seconds. - js: | - ```js - const { data, error } = await supabase.auth.signInWithOtp({ - phone: '+13334445555', - }) - ``` - auth.signInWithOAuth(): - title: 'signInWithOAuth()' - $ref: '@supabase/gotrue-js.GoTrueClient.signInWithOAuth' - notes: | - - This method is used for signing in using a third-party provider. - - Supabase supports many different [third-party providers](https://supabase.com/docs/guides/auth#providers). - examples: - - name: Sign in using a third-party provider - isSpotlight: true - js: | - ```js - const { data, error } = await supabase.auth.signInWithOAuth({ - provider: 'github' - }) - ``` - - name: Sign in using a third-party provider with redirect - isSpotlight: false - description: | - When the third-party provider successfully authenticates the user, the provider redirects the user to the Supabase Auth callback URL where they are further redirected to the URL specified in the `redirectTo` parameter. This parameter defaults to the [`SITE_URL`](/docs/reference/auth/config#site_url). - You can modify the `SITE_URL` or [add additional redirect URLs](https://app.supabase.com/project/_/auth/settings). You can use [wildcard match patterns](/docs/guides/auth#redirect-urls-and-wildcards) to support preview URLs from providers like Netlify and Vercel. - js: | - ```js - const { data, error } = await supabase.auth.signInWithOAuth({ - provider: 'github' - options: { - redirectTo: 'https://example.com/welcome' - } - } - ``` - - name: Sign in with scopes - isSpotlight: false - description: | - If you need additional data from an OAuth provider, you can include a space-separated list of scopes in your request to get back an OAuth provider token. - You may also need to specify the scopes in the provider's OAuth app settings, depending on the provider. The list of scopes will be documented by the third-party provider you are using and specifying scopes will enable you to use the OAuth provider token to call additional APIs supported by the third-party provider to get more information. - js: | - ```js - const { data, error } = await supabase.auth.signInWithOAuth({ - provider: 'github' - options: { - scopes: 'repo gist notifications' - } - }) - const oAuthToken = data.session.provider_token // use to access provider API - ``` - auth.signOut(): - title: 'signOut()' - $ref: '@supabase/gotrue-js.GoTrueClient.signOut' - notes: | - - In order to use the `signOut()` method, the user needs to be signed in first. - examples: - - name: Sign out - isSpotlight: true - js: | - ```js - const { error } = await supabase.auth.signOut() - ``` - auth.verifyOtp(): - title: 'verifyOtp()' - $ref: '@supabase/gotrue-js.GoTrueClient.verifyOtp' - notes: | - - The `verifyOtp` method takes in different verification types. If a phone number is used, the type can either be `sms` or `phone_change`. If an email address is used, the type can be one of the following: `signup`, `magiclink`, `recovery`, `invite` or `email_change`. - - The verification type used should be determined based on the corresponding auth method called before `verifyOtp` to sign up / sign-in a user. - examples: - - name: Verify Sms One-Time Password (OTP) - isSpotlight: true - js: | - ```js - const { data, error } = await supabase.auth.verifyOtp({ phone, token, type: 'sms'}) - ``` - - name: Verify Signup One-Time Password (OTP) - isSpotlight: false - js: | - ```js - const { data, error } = await supabase.auth.verifyOtp({ email, token, type: 'signup'}) - ``` - auth.getSession(): - title: 'getSession()' - $ref: '@supabase/gotrue-js.GoTrueClient.getSession' - examples: - - name: Get the session data - isSpotlight: true - js: | - ```js - const { data, error } = await supabase.auth.getSession() - ``` - auth.getUser(): - title: 'getUser()' - $ref: '@supabase/gotrue-js.GoTrueClient.getUser' - notes: | - - This method gets the user object from the current session. - - Fetches the user object from the database instead of local session. - examples: - - name: Get the logged in user with the current existing session - isSpotlight: true - js: | - ```js - const { data: { user } } = await supabase.auth.getUser() - ``` - - name: Get the logged in user with a custom access token jwt. - isSpotlight: false - js: | - ```js - const { data: { user } } = await supabase.auth.getUser(jwt) - ``` - auth.updateUser(): - title: 'updateUser()' - $ref: '@supabase/gotrue-js.GoTrueClient.updateUser' - notes: | - - In order to use the `updateUser()` method, the user needs to be signed in first. - - By Default, email updates sends a confirmation link to both the user's current and new email. - To only send a confirmation link to the user's new email, disable **Secure email change** in your project's [email auth provider settings](https://app.supabase.com/project/_/auth/settings). - examples: - - name: Update the email for an authenticated user - description: Sends a "Confirm Email Change" email to the new email address. - isSpotlight: false - js: | - ```js - const { data, error } = await supabase.auth.updateUser({email: 'new@email.com'}) - ``` - - name: Update the password for an authenticated user - isSpotlight: false - js: | - ```js - const { data, error } = await supabase.auth.updateUser({password: 'new password'}) - ``` - - name: Update the user's metadata - isSpotlight: true - js: | - ```js - const { data, error } = await supabase.auth.updateUser({ - data: { hello: 'world' } - }) - ``` - auth.setSession(): - title: 'setSession()' - $ref: '@supabase/gotrue-js.GoTrueClient.setSession' - notes: | - - `setSession()` takes in a refresh token and access token to get a new session. - - The refresh token can only be used once to obtain a new session. - - [Refresh token rotation](/docs/reference/auth/config#refresh_token_rotation_enabled) is enabled by default on all projects to guard against replay attacks. - - You can configure the [`REFRESH_TOKEN_REUSE_INTERVAL`](https://supabase.com/docs/reference/auth/config#refresh_token_reuse_interval) which provides a short window in which the same refresh token can be used multiple times in the event of concurrency or offline issues. - - If you are using React Native, you will need to install a Buffer polyfill via a library such as [rn-nodeify](https://github.com/tradle/rn-nodeify) to properly use the library. - examples: - - name: Set the session - description: Sets the session data from a `refresh_token` and `access_token` and returns the current session or an error if the tokens are invalid. - isSpotlight: true - js: | - ```js - const { data, error } = supabase.auth.setSession({refresh_token, access_token}) - ``` - auth.refreshSession(): - title: 'refreshSession()' - $ref: '@supabase/gotrue-js.GoTrueClient.refreshSession' - notes: | - - This method will refresh the session whether the current one is expired or not. - - Both examples destructure `user` and `session` from `data`. This is not required; so `const { data, error } =` is also valid. - examples: - - name: Refresh session using the current session - isSpotlight: true - js: | - ```js - const { data: { user, session }, error } = await supabase.auth.refreshSession() - ``` - - name: Refresh session using a passed-in session - isSpotlight: true - js: | - ```js - const { data: { user, session }, error } = await supabase.auth.refreshSession({ refresh_token }) - ``` - auth.onAuthStateChange(): - title: 'onAuthStateChange()' - $ref: '@supabase/gotrue-js.GoTrueClient.onAuthStateChange' - notes: | - - Types of auth events: `SIGNED_IN`, `SIGNED_OUT`, `TOKEN_REFRESHED`, `USER_UPDATED`, `USER_DELETED`, `PASSWORD_RECOVERY` - examples: - - name: Listen to auth changes - isSpotlight: true - js: | - ```js - supabase.auth.onAuthStateChange((event, session) => { - console.log(event, session) - }) - ``` - - name: Listen to sign in - js: | - ```js - supabase.auth.onAuthStateChange((event, session) => { - if (event == 'SIGNED_IN') console.log('SIGNED_IN', session) - }) - ``` - - name: Listen to sign out - js: | - ```js - supabase.auth.onAuthStateChange((event, session) => { - if (event == 'SIGNED_OUT') console.log('SIGNED_OUT', session) - }) - ``` - - name: Listen to token refresh - js: | - ```js - supabase.auth.onAuthStateChange((event, session) => { - if (event == 'TOKEN_REFRESHED') console.log('TOKEN_REFRESHED', session) - }) - ``` - - name: Listen to user updates - js: | - ```js - supabase.auth.onAuthStateChange((event, session) => { - if (event == 'USER_UPDATED') console.log('USER_UPDATED', session) - }) - ``` - - name: Listen to user deleted - js: | - ```js - supabase.auth.onAuthStateChange((event, session) => { - if (event == 'USER_DELETED') console.log('USER_DELETED', session) - }) - ``` - - name: Listen to password recovery events - js: | - ```js - supabase.auth.onAuthStateChange((event, session) => { - if (event == 'PASSWORD_RECOVERY') console.log('PASSWORD_RECOVERY', session) - }) - ``` - Supabase Auth Admin Api: - title: 'Overview' - notes: | - - Any method under the `supabase.auth.admin` namespace requires a `service_role` key. - - These methods are considered admin methods and should be called on a trusted server. Never expose your `service_role` key in the browser. - - auth.admin.getUserById(): - title: 'getUserById()' - $ref: '@supabase/gotrue-js.GoTrueAdminApi.getUserById' - notes: | - - Fetches the user object from the database based on the user's id. - - The `getUserById()` method requires the user's id which maps to the `auth.users.id` column. - examples: - - name: Fetch the user object using the access_token jwt. - isSpotlight: true - js: | - ```js - const { data, error } = await supabase.auth.admin.getUserById(1) - ``` - - auth.admin.listUsers(): - title: 'listUsers()' - $ref: '@supabase/gotrue-js.GoTrueAdminApi.listUsers' - examples: - - name: Get a full list of users. - isSpotlight: true - js: | - ```js - const { data: { users }, error } = await supabase.auth.admin.listUsers() - ``` - auth.admin.createUser(): - title: 'createUser()' - $ref: '@supabase/gotrue-js.GoTrueAdminApi.createUser' - notes: | - - To confirm the user's email address or phone number, set `email_confirm` or `phone_confirm` to true. Both arguments default to false. - examples: - - name: Create a new user with custom user metadata - isSpotlight: true - js: | - ```js - const { data, error } = await supabase.auth.admin.createUser({ - email: 'user@email.com', - password: 'password', - user_metadata: { name: 'Yoda' } - }) - ``` - - name: Auto-confirm the user's email - js: | - ```js - const { data, error } = await supabase.auth.admin.createUser({ - email: 'user@email.com', - email_confirm: true - }) - ``` - - name: Auto-confirm the user's phone number - js: | - ```js - const { data, error } = await supabase.auth.admin.createUser({ - phone: '1234567890', - phone_confirm: true - }) - ``` - auth.admin.deleteUser(): - title: 'deleteUser()' - $ref: '@supabase/gotrue-js.GoTrueAdminApi.deleteUser' - notes: | - - The `deleteUser()` method requires the user's ID, which maps to the `auth.users.id` column. - examples: - - name: Removes a user - isSpotlight: true - js: | - ```js - const { data, error } = await supabase.auth.admin.deleteUser( - '715ed5db-f090-4b8c-a067-640ecee36aa0' - ) - ``` - - auth.admin.inviteUserByEmail(): - title: 'inviteUserByEmail()' - $ref: '@supabase/gotrue-js.GoTrueAdminApi.inviteUserByEmail' - notes: | - - Sends an invite link to the user's email address. - examples: - - name: Invite a user - isSpotlight: true - js: | - ```js - const { data, error } = await supabase.auth.admin.inviteUserByEmail('email@example.com') - ``` - - auth.resetPasswordForEmail(): - title: 'resetPasswordForEmail()' - $ref: '@supabase/gotrue-js.GoTrueClient.resetPasswordForEmail' - notes: | - Sends a password reset request to an email address. - When the user clicks the reset link in the email they are redirected back to your application. - Prompt the user for a new password and call `auth.updateUser()`: - ```js - const { data, error } = await supabase.auth - .updateUser({ password: new_password }) - ``` - examples: - - name: Reset password - isSpotlight: true - js: | - ```js - const { data, error } = await supabase.auth.resetPasswordForEmail(email, { - redirectTo: 'https://example.com/update-password', - }) - ``` - - name: Reset password (React) - isSpotlight: true - js: | - ```js - /** - * Step 1: Send the user an email to get a password reset token. - * This email contains a link which sends the user back to your application. - */ - const { data, error } = await supabase.auth - .resetPasswordForEmail('user@email.com') - - /** - * Step 2: Once the user is redirected back to your application, - * ask the user to reset their password. - */ - useEffect(() => { - supabase.auth.onAuthStateChange(async (event, session) => { - if (event == "PASSWORD_RECOVERY") { - const newPassword = prompt("What would you like your new password to be?"); - const { data, error } = await supabase.auth - .updateUser({ password: newPassword }) - - if (data) alert("Password updated successfully!") - if (error) alert("There was an error updating your password.") - } - }) - }, []) - ``` - - auth.admin.generateLink(): - title: 'generateLink()' - $ref: '@supabase/gotrue-js.GoTrueAdminApi.generateLink' - examples: - - name: Generate a signup link - isSpotlight: true - js: | - ```js - const { data, error } = await supabase.auth.admin.generateLink({ - type: 'signup', - email: 'email@example.com', - options: { - 'password': 'secret' - } - }) - ``` - - name: Generate an invite link - isSpotlight: false - js: | - ```js - const { data, error } = await supabase.auth.admin.generateLink({ - type: 'invite', - email: 'email@example.com' - }) - ``` - - name: Generate a magic link - isSpotlight: false - js: | - ```js - const { data, error } = await supabase.auth.admin.generateLink({ - type: 'magiclink', - email: 'email@example.com' - }) - ``` - - name: Generate a recovery link - isSpotlight: false - js: | - ```js - const { data, error } = await supabase.auth.admin.generateLink({ - type: 'recovery', - email: 'email@example.com' - }) - ``` - - name: Generate links to change current email address - isSpotlight: false - js: | - ```js - // generate an email change link to be sent to the current email address - const { data, error } = await supabase.auth.admin.generateLink({ - type: 'email_change_current', - email: 'current.email@example.com', - newEmail: 'new.email@example.com' - }) - - // generate an email change link to be sent to the new email address - const { data, error } = await supabase.auth.admin.generateLink({ - type: 'email_change_new', - email: 'current.email@example.com', - newEmail: 'new.email@example.com' - }) - ``` - - auth.admin.updateUserById(): - title: 'updateUserById()' - $ref: '@supabase/gotrue-js.GoTrueAdminApi.updateUserById' - examples: - - name: Updates a user's email. - isSpotlight: false - js: | - ```js - const { data: user, error } = await supabase.auth.admin.updateUserById( - '6aa5d0d4-2a9f-4483-b6c8-0cf4c6c98ac4', - { email: 'new@email.com' } - ) - ``` - - name: Updates a user's password. - isSpotlight: false - js: | - ```js - const { data: user, error } = await supabase.auth.admin.updateUserById( - '6aa5d0d4-2a9f-4483-b6c8-0cf4c6c98ac4', - { password: 'new_password' } - ) - ``` - - name: Updates a user's metadata. - isSpotlight: true - js: | - ```js - const { data: user, error } = await supabase.auth.admin.updateUserById( - '6aa5d0d4-2a9f-4483-b6c8-0cf4c6c98ac4', - { user_metadata: { hello: 'world' } } - ) - ``` - - name: Updates a user's app_metadata. - isSpotlight: false - js: | - ```js - const { data: user, error } = await supabase.auth.admin.updateUserById( - '6aa5d0d4-2a9f-4483-b6c8-0cf4c6c98ac4', - { app_metadata: { plan: 'trial' } } - ) - ``` - - name: Confirms a user's email address. - isSpotlight: false - js: | - ```js - const { data: user, error } = await supabase.auth.admin.updateUserById( - '6aa5d0d4-2a9f-4483-b6c8-0cf4c6c98ac4', - { email_confirm: true } - ) - ``` - - name: Confirms a user's phone number. - isSpotlight: false - js: | - ```js - const { data: user, error } = await supabase.auth.admin.updateUserById( - '6aa5d0d4-2a9f-4483-b6c8-0cf4c6c98ac4', - { phone_confirm: true } - ) - ``` - - select(): - title: 'Fetch data: select()' - $ref: '@supabase/postgrest-js.PostgrestQueryBuilder.select' - notes: | - - By default, Supabase projects return a maximum of 1,000 rows. This setting can be changed in your project's [API settings](https://app.supabase.com/project/_/settings/api). It's recommended that you keep it low to limit the payload size of accidental or malicious requests. You can use `range()` queries to paginate through your data. - - `select()` can be combined with [Filters](/docs/reference/javascript/using-filters) - - `select()` can be combined with [Modifiers](/docs/reference/javascript/using-modifiers) - - `apikey` is a reserved keyword if you're using the [Supabase Platform](/docs/guides/platform) and should be avoided as a column name](https://github.com/supabase/supabase/issues/5465). - examples: - - name: Getting your data - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Afghanistan" - }, - { - "id": 2, - "name": "Albania" - }, - { - "id": 3, - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select() - ``` - - name: Selecting specific columns - description: | - You can select specific fields from your tables. - - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - ``` - - - - - ```json - { - "data": [ - { - "name": "Afghanistan" - }, - { - "name": "Albania" - }, - { - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - name: Query foreign tables - description: | - If your database has foreign key relationships, you can query related tables too. - - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'Germany'), - (2, 'Indonesia'); - insert into - cities (id, country_id, name) - values - (1, 2, 'Bali'), - (2, 1, 'Munich'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select(` - name, - cities ( - name - ) - `) - ``` - - - - - ```json - { - "data": [ - { - "name": "Germany", - "cities": [ - { - "name": "Munich" - } - ] - }, - { - "name": "Indonesia", - "cities": [ - { - "name": "Bali" - } - ] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - name: Query foreign tables through a join table - description: | - If you're in a situation where your tables are **NOT** directly - related, but instead are joined by a _join table_, you can still use - the `select()` method to query the related data. The join table needs - to have the foreign keys as part of its composite primary key. - - - - - ```sql - create table - users ( - id int8 primary key, - name text - ); - create table - teams ( - id int8 primary key, - name text - ); - -- join table - create table - users_teams ( - user_id int8 not null references users, - team_id int8 not null references teams, - -- both foreign keys must be part of a composite primary key - primary key (user_id, team_id) - ); - - insert into - users (id, name) - values - (1, 'Kiran'), - (2, 'Evan'); - insert into - teams (id, name) - values - (1, 'Green'), - (2, 'Blue'); - insert into - users_teams (user_id, team_id) - values - (1, 1), - (1, 2), - (2, 2); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .select(` - name, - teams ( - name - ) - `) - ``` - - - - - ```json - { - "data": [ - { - "name": "Kiran", - "teams": [ - { - "name": "Green" - }, - { - "name": "Blue" - } - ] - }, - { - "name": "Evan", - "teams": [ - { - "name": "Blue" - } - ] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - name: Query the same foreign table multiple times - description: | - If you need to query the same foreign table twice, use the name of the - joined column to identify which join to use. You can also give each - column an alias. - - - - - ```sql - create table - users (id int8 primary key, name text); - - create table - messages ( - sender_id int8 not null references users, - receiver_id int8 not null references users, - content text - ); - - insert into - users (id, name) - values - (1, 'Kiran'), - (2, 'Evan'); - - insert into - messages (sender_id, receiver_id, content) - values - (1, 2, '👋'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('messages') - .select(` - content, - from:sender_id(name), - to:receiver_id(name) - `) - ``` - - - - - ```json - { - "data": [ - { - "content": "👋", - "from": { - "name": "Kiran" - }, - "to": { - "name": "Evan" - } - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - name: Filtering through foreign tables - description: | - If the filter on a foreign table's column is not satisfied, the foreign - table returns `[]` or `null` but the parent table is not filtered out. - - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'Germany'), - (2, 'Indonesia'); - insert into - cities (id, country_id, name) - values - (1, 2, 'Bali'), - (2, 1, 'Munich'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('cities') - .select('name, countries(*)') - .eq('countries.name', 'Estonia') - ``` - - - - - ```json - { - "data": [ - { - "name": "Bali", - "countries": null - }, - { - "name": "Munich", - "countries": null - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - - If you want to filter out the parent table rows, use the `!inner` hint: - - - - - ```ts - const { data, error } = await supabase - .from('cities') - .select('name, countries!inner(*)') - .eq('countries.name', 'Estonia') - ``` - - - - - ```json - { - "data": [], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - name: Querying with count option - description: | - You can get the number of rows by using the - [count](/docs/reference/javascript/select#parameters) option. For - example, to get the table count without returning all rows: - - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { count, error } = await supabase - .from('countries') - .select('*', { count: 'exact', head: true }) - ``` - - - - - ```json - { - "count": 3, - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - name: Querying JSON data - description: | - You can select and filter data inside of - [JSON](/docs/guides/database/json) columns. Postgres offers some - [operators](/docs/guides/database/json#query-the-jsonb-data) for - querying JSON data. - - - - - ```sql - create table - users ( - id int8 primary key, - name text, - address jsonb - ); - - insert into - users (id, name, address) - values - (1, 'Avdotya', '{"city":"Saint Petersburg"}'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .select(` - id, name, - address->city - `) - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Avdotya", - "city": "Saint Petersburg" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - insert(): - title: 'Create data: insert()' - $ref: '@supabase/postgrest-js.PostgrestQueryBuilder.insert' - examples: - - name: Create a record - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - ``` - - - - - ```ts - const { error } = await supabase - .from('countries') - .insert({ id: 1, name: 'Denmark' }) - ``` - - - - - ```json - { - "status": 201, - "statusText": "Created" - } - ``` - - - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Denmark" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { error } = await supabase - .from('countries') - .insert({ id: 1, name: 'Denmark' }) - ``` - - name: Create a record and return it - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .insert({ id: 1, name: 'Denmark' }) - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Denmark" - } - ], - "status": 201, - "statusText": "Created" - } - ``` - - - - hideCodeBlock: true - - name: Bulk create - description: | - A bulk create operation is handled in a single transaction. - If any of the inserts fail, none of the rows are inserted. - - - - - ```sql - create table - countries (id int8 primary key, name text); - ``` - - - - - ```ts - const { error } = await supabase - .from('countries') - .insert([ - { id: 1, name: 'Nepal' }, - { id: 1, name: 'Vietnam' }, - ]) - ``` - - - - - ```json - { - "error": { - "code": "23505", - "details": "Key (id)=(1) already exists.", - "hint": null, - "message": "duplicate key value violates unique constraint \"countries_pkey\"" - }, - "status": 409, - "statusText": "Conflict" - } - ``` - - - - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - ``` - - - - - ```json - { - "data": [], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - update(): - title: 'Modify data: update()' - $ref: '@supabase/postgrest-js.PostgrestQueryBuilder.update' - notes: | - - `update()` should always be combined with [Filters](/docs/reference/javascript/using-filters) to target the item(s) you wish to update. - examples: - - name: Updating your data - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Taiwan'); - ``` - - - - - ```ts - const { error } = await supabase - .from('countries') - .update({ name: 'Australia' }) - .eq('id', 1) - ``` - - - - - ```json - { - "status": 204, - "statusText": "No Content" - } - ``` - - - - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Australia" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { error } = await supabase - .from('countries') - .update({ name: 'Australia' }) - .eq('id', 1) - ``` - - name: Update a record and return it - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Taiwan'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .update({ name: 'Australia' }) - .eq('id', 1) - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Australia" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - name: Updating JSON data - description: | - Postgres offers some - [operators](/docs/guides/database/json#query-the-jsonb-data) for - working with JSON data. - - - - - ```sql - create table - users ( - id int8 primary key, - name text, - address jsonb - ); - - insert into - users (id, name, address) - values - (1, 'Michael', '{ "postcode": 90210 }'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .update({ - address: { - street: 'Melrose Place', - postcode: 90210 - } - }) - .eq('address->postcode', 90210) - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Michael", - "address": { - "street": "Melrose Place", - "postcode": 90210 - } - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - - Currently, it is only possible to update the entire JSON document. - hideCodeBlock: true - - upsert(): - title: 'Upsert data: upsert()' - $ref: '@supabase/postgrest-js.PostgrestQueryBuilder.upsert' - notes: | - - Primary keys must be included in `values` to use upsert. - examples: - - name: Upsert your data - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .upsert({ id: 1, name: 'Albania' }) - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Albania" - } - ], - "status": 201, - "statusText": "Created" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .upsert({ id: 1, name: 'Albania' }) - .select() - ``` - - name: Bulk Upsert your data - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .upsert([ - { id: 1, name: 'Albania' }, - { id: 2, name: 'Algeria' }, - ]) - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Albania" - }, - { - "id": 2, - "name": "Algeria" - } - ], - "status": 201, - "statusText": "Created" - } - ``` - - - - hideCodeBlock: true - - name: Upserting into tables with constraints - description: | - In the following query, `upsert()` implicitly uses the `id` - (primary key) column to determine conflicts. If there is no existing - row with the same `id`, `upsert()` inserts a new row, which - will fail in this case as there is already a row with `handle` `"saoirse"`. - - - - - ```sql - create table - users ( - id int8 generated by default as identity primary key, - handle text not null unique, - display_name text - ); - - insert into - users (id, handle, display_name) - values - (1, 'saoirse', null); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .upsert({ id: 42, handle: 'saoirse', display_name: 'Saoirse' }) - .select() - ``` - - - - - ```json - { - "error": { - "code": "23505", - "details": "Key (handle)=(saoirse) already exists.", - "hint": null, - "message": "duplicate key value violates unique constraint \"users_handle_key\"" - }, - "status": 409, - "statusText": "Conflict" - } - ``` - - - - - Using the `onConflict` option, you can instruct `upsert()` to use - another column with a unique constraint to determine conflicts: - - - - - ```ts - await supabase - .from('users') - .upsert( - { id: 42, handle: 'saoirse', display_name: 'Saoirse' }, - { onConflict: 'handle' }, - ) - const { data, error } = await supabase - .from('users') - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 42, - "handle": "saoirse", - "display_name": "Saoirse" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - delete(): - title: 'Delete data: delete()' - $ref: '@supabase/postgrest-js.PostgrestQueryBuilder.delete' - notes: | - - `delete()` should always be combined with [filters](/docs/reference/javascript/using-filters) to target the item(s) you wish to delete. - - If you use `delete()` with filters and you have - [RLS](/docs/learn/auth-deep-dive/auth-row-level-security) enabled, only - rows visible through `SELECT` policies are deleted. Note that by default - no rows are visible, so you need at least one `SELECT`/`ALL` policy that - makes the rows visible. - examples: - - name: Delete records - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Spain'); - ``` - - - - - ```ts - const { error } = await supabase - .from('countries') - .delete() - .eq('id', 1) - ``` - - - - - ```json - { - "status": 204, - "statusText": "No Content" - } - ``` - - - - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - ``` - - - - - ```json - { - "data": [], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { error } = await supabase - .from('countries') - .delete() - .eq('id', 1) - ``` - - rpc(): - title: 'Postgres functions: rpc()' - description: | - You can call Postgres functions as _Remote Procedure Calls_, logic in your database that you can execute from anywhere. - Functions are useful when the logic rarely changes—like for password resets and updates. - - ```sql - create or replace function hello_world() returns text as $$ - select 'Hello world'; - $$ language sql; - ``` - $ref: '@supabase/postgrest-js.PostgrestClient.rpc' - examples: - - name: Call a Postgres function without arguments - description: | - - - - ```sql - create function hello_world() returns text as $$ - select 'Hello world'; - $$ language sql; - ``` - - - - - ```ts - const { data, error } = await supabase.rpc('hello_world') - ``` - - - - - ```json - { - "data": "Hello world", - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase.rpc('hello_world') - ``` - - name: Call a Postgres function with arguments - description: | - - - - ```sql - create function echo(say text) returns text as $$ - select say; - $$ language sql; - ``` - - - - - ```ts - const { data, error } = await supabase.rpc('echo', { say: '👋' }) - ``` - - - - - ```json - { - "data": "👋", - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - name: Bulk processing - description: | - You can process large payloads by passing in an array as an argument: - - - - - ```sql - create function add_one_each(arr int[]) returns int[] as $$ - select array_agg(n + 1) from unnest(arr) as n; - $$ language sql; - ``` - - - - - ```ts - const { data, error } = await supabase.rpc('add_one_each', { arr: [1, 2, 3] }) - ``` - - - - - ```json - { - "data": [ - 2, - 3, - 4 - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - name: Call a Postgres function with filters - description: | - Postgres functions that return tables can also be combined with - [Filters](/docs/reference/javascript/using-filters) and - [Modifiers](/docs/reference/javascript/using-modifiers). - - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'France'), - (2, 'United Kingdom'); - - create function list_stored_countries() returns setof countries as $$ - select * from countries; - $$ language sql; - ``` - - - - - ```ts - const { data, error } = await supabase - .rpc('list_stored_countries') - .eq('id', 1) - .single() - ``` - - - - - ```json - { - "data": { - "id": 1, - "name": "France" - }, - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - Using Filters: - description: | - Filters allow you to only return rows that match certain conditions. - - Filters can be used on `select()`, `update()`, `upsert()`, and `delete()` queries. - - If a Postgres function returns a table response, you can also apply filters. - - ### Applying Filters - - Filters must be applied after any of `select()`, `update()`, `upsert()`, - `delete()`, and `rpc()` and before - [modifiers](/docs/reference/javascript/using-modifiers). - - ```ts - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .eq('name', 'The Shire') // Correct - - const { data, error } = await supabase - .from('cities') - .eq('name', 'The Shire') // Incorrect - .select('name, country_id') - ``` - - ### Chaining - - Filters can be chained together to produce advanced queries. For example, - to query cities with population between 1,000 and 10,000: - - ```ts - const { data, error } = await supabase - .from('cities') - .select('name, country_id') - .gte('population', 1000) - .lt('population', 10000) - ``` - - ### Conditional Chaining - - Filters can be built up one step at a time and then executed. For example: - - ```ts - const filterByName = null - const filterPopLow = 1000 - const filterPopHigh = 10000 - - let query = supabase - .from('cities') - .select('name, country_id') - - if (filterByName) { query = query.eq('name', filterByName) } - if (filterPopLow) { query = query.gte('population', filterPopLow) } - if (filterPopHigh) { query = query.lt('population', filterPopHigh) } - - const { data, error } = await query - ``` - - ### Filter by values within a JSON column - - - - - ```sql - create table - users ( - id int8 primary key, - name text, - address jsonb - ); - - insert into - users (id, name, address) - values - (1, 'Michael', '{ "postcode": 90210 }'), - (2, 'Jane', null); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .select() - .eq('address->postcode', 90210) - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Michael", - "address": { - "postcode": 90210 - } - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - - ### Filter Foreign Tables - - You can filter on foreign tables in your `select()` query using dot - notation: - - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'Germany'), - (2, 'Indonesia'); - insert into - cities (id, country_id, name) - values - (1, 2, 'Bali'), - (2, 1, 'Munich'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select(` - name, - cities!inner ( - name - ) - `) - .eq('cities.name', 'Bali') - ``` - - - - - ```json - { - "data": [ - { - "name": "Indonesia", - "cities": [ - { - "name": "Bali" - } - ] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - - eq(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.eq' - examples: - - name: With `select()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .eq('name', 'Albania') - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "name": "Albania" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .eq('name', 'Albania') - ``` - - neq(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.neq' - examples: - - name: With `select()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .neq('name', 'Albania') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Afghanistan" - }, - { - "id": 3, - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .neq('name', 'Albania') - ``` - - gt(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.gt' - examples: - - name: With `select()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .gt('id', 2) - ``` - - - - - ```json - { - "data": [ - { - "id": 3, - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .gt('id', 2) - ``` - - gte(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.gte' - examples: - - name: With `select()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .gte('id', 2) - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "name": "Albania" - }, - { - "id": 3, - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .gte('id', 2) - ``` - - lt(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.lt' - examples: - - name: With `select()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .lt('id', 2) - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Afghanistan" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .lt('id', 2) - ``` - - lte(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.lte' - examples: - - name: With `select()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .lte('id', 2) - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Afghanistan" - }, - { - "id": 2, - "name": "Albania" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .lte('id', 2) - ``` - - like(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.like' - examples: - - name: With `select()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .like('name', '%Alba%') - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "name": "Albania" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .like('name', '%Alba%') - ``` - - ilike(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.ilike' - examples: - - name: With `select()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .ilike('name', '%alba%') - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "name": "Albania" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .ilike('name', '%alba%') - ``` - - is(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.is' - examples: - - name: Checking nullness - description: | - Using the `eq()` filter doesn't work when filtering for `null`: - - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'null'), - (2, null); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .eq('name', null) - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "null" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - - Instead, you need to use `is()`: - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .is('name', null) - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "name": null - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .is('name', null) - ``` - - in(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.in' - examples: - - name: With `select()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .in('name', ['Albania', 'Algeria']) - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "name": "Albania" - }, - { - "id": 3, - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .in('name', ['Albania', 'Algeria']) - ``` - - contains(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.contains' - examples: - - name: On array columns - description: | - - - - ```sql - create table - issues ( - id int8 primary key, - title text, - tags text[] - ); - - insert into - issues (id, title, tags) - values - (1, 'Cache invalidation is not working', array['is:open', 'severity:high', 'priority:low']), - (2, 'Use better names', array['is:open', 'severity:low', 'priority:medium']); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('issues') - .select('title') - .contains('tags', ['is:open', 'severity:high']) - ``` - - - - - ```json - { - "data": [ - { - "title": "Cache invalidation is not working" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('users') - .select() - .contains('name', ['is:online', 'faction:red']) - ``` - - name: On range columns - description: | - Postgres supports a number of [range - types](https://www.postgresql.org/docs/current/rangetypes.html). You - can filter on range columns using the string representation of range - values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .contains('during', '[2000-01-01 13:00, 2000-01-01 13:30)') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "room_name": "Emerald", - "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - name: On `jsonb` columns - description: | - - - - ```sql - create table - users ( - id int8 primary key, - name text, - address jsonb - ); - - insert into - users (id, name, address) - values - (1, 'Michael', '{ "postcode": 90210, "street": "Melrose Place" }'), - (2, 'Jane', '{}'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .select('name') - .contains('address', { postcode: 90210 }) - ``` - - - - - ```json - { - "data": [ - { - "name": "Michael" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - containedBy(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.containedBy' - examples: - - name: On array columns - description: | - - - - ```sql - create table - classes ( - id int8 primary key, - name text, - days text[] - ); - - insert into - classes (id, name, days) - values - (1, 'Chemistry', array['monday', 'friday']), - (2, 'History', array['monday', 'wednesday', 'thursday']); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('classes') - .select('name') - .containedBy('days', ['monday', 'tuesday', 'wednesday', 'friday']) - ``` - - - - - ```json - { - "data": [ - { - "name": "Chemistry" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('classes') - .select('name') - .containedBy('days', ['monday', 'tuesday', 'wednesday', 'friday']) - ``` - - name: On range columns - description: | - Postgres supports a number of [range - types](https://www.postgresql.org/docs/current/rangetypes.html). You - can filter on range columns using the string representation of range - values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .containedBy('during', '[2000-01-01 00:00, 2000-01-01 23:59)') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "room_name": "Emerald", - "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - name: On `jsonb` columns - description: | - - - - ```sql - create table - users ( - id int8 primary key, - name text, - address jsonb - ); - - insert into - users (id, name, address) - values - (1, 'Michael', '{ "postcode": 90210, "street": "Melrose Place" }'), - (2, 'Jane', '{}'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('users') - .select('name') - .containedBy('address', {}) - ``` - - - - - ```json - { - "data": [ - { - "name": "Jane" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - rangeGt(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.rangeGt' - examples: - - name: With `select()` - description: | - Postgres supports a number of [range - types](https://www.postgresql.org/docs/current/rangetypes.html). You - can filter on range columns using the string representation of range - values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeGt('during', '[2000-01-02 08:00, 2000-01-02 09:00)') - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "room_name": "Topaz", - "during": "[\"2000-01-02 09:00:00\",\"2000-01-02 10:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeGt('during', '[2000-01-02 08:00, 2000-01-02 09:00)') - ``` - - rangeGte(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.rangeGte' - examples: - - name: With `select()` - description: | - Postgres supports a number of [range - types](https://www.postgresql.org/docs/current/rangetypes.html). You - can filter on range columns using the string representation of range - values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeGte('during', '[2000-01-02 08:30, 2000-01-02 09:30)') - ``` - - - - - ```json - { - "data": [ - { - "id": 2, - "room_name": "Topaz", - "during": "[\"2000-01-02 09:00:00\",\"2000-01-02 10:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeGte('during', '[2000-01-02 08:30, 2000-01-02 09:30)') - ``` - - rangeLt(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.rangeLt' - examples: - - name: With `select()` - description: | - Postgres supports a number of [range - types](https://www.postgresql.org/docs/current/rangetypes.html). You - can filter on range columns using the string representation of range - values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeLt('during', '[2000-01-01 15:00, 2000-01-01 16:00)') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "room_name": "Emerald", - "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeLt('during', '[2000-01-01 15:00, 2000-01-01 16:00)') - ``` - - rangeLte(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.rangeLte' - examples: - - name: With `select()` - description: | - Postgres supports a number of [range - types](https://www.postgresql.org/docs/current/rangetypes.html). You - can filter on range columns using the string representation of range - values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeLte('during', '[2000-01-01 14:00, 2000-01-01 16:00)') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "room_name": "Emerald", - "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeLte('during', '[2000-01-01 14:00, 2000-01-01 16:00)') - ``` - - rangeAdjacent(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.rangeAdjacent' - examples: - - name: With `select()` - description: | - Postgres supports a number of [range - types](https://www.postgresql.org/docs/current/rangetypes.html). You - can filter on range columns using the string representation of range - values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeAdjacent('during', '[2000-01-01 12:00, 2000-01-01 13:00)') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "room_name": "Emerald", - "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .rangeAdjacent('during', '[2000-01-01 12:00, 2000-01-01 13:00)') - ``` - - overlaps(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.overlaps' - examples: - - name: On array columns - description: | - - - - ```sql - create table - issues ( - id int8 primary key, - title text, - tags text[] - ); - - insert into - issues (id, title, tags) - values - (1, 'Cache invalidation is not working', array['is:open', 'severity:high', 'priority:low']), - (2, 'Use better names', array['is:open', 'severity:low', 'priority:medium']); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('issues') - .select('title') - .overlaps('tags', ['is:closed', 'severity:high']) - ``` - - - - - ```json - { - "data": [ - { - "title": "Cache invalidation is not working" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('issues') - .select('title') - .overlaps('tags', ['is:closed', 'severity:high']) - ``` - - name: On range columns - description: | - Postgres supports a number of [range - types](https://www.postgresql.org/docs/current/rangetypes.html). You - can filter on range columns using the string representation of range - values. - - - - - ```sql - create table - reservations ( - id int8 primary key, - room_name text, - during tsrange - ); - - insert into - reservations (id, room_name, during) - values - (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), - (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('reservations') - .select() - .overlaps('during', '[2000-01-01 12:45, 2000-01-01 13:15)') - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "room_name": "Emerald", - "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - # TODO: schema & result - textSearch(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.textSearch' - notes: - - For more information, see [Postgres full text search](/docs/guides/database/full-text-search). - examples: - - name: Text search - js: | - ```ts - const { data, error } = await supabase - .from('quotes') - .select('catchphrase') - .textSearch('catchphrase', `'fat' & 'cat'`, { - config: 'english' - }) - ``` - - name: Basic normalization - description: Uses PostgreSQL's `plainto_tsquery` function. - js: | - ```ts - const { data, error } = await supabase - .from('quotes') - .select('catchphrase') - .textSearch('catchphrase', `'fat' & 'cat'`, { - type: 'plain', - config: 'english' - }) - ``` - - name: Full normalization - description: Uses PostgreSQL's `phraseto_tsquery` function. - js: | - ```ts - const { data, error } = await supabase - .from('quotes') - .select('catchphrase') - .textSearch('catchphrase', `'fat' & 'cat'`, { - type: 'phrase', - config: 'english' - }) - ``` - - name: Websearch - description: | - Uses PostgreSQL's `websearch_to_tsquery` function. - This function will never raise syntax errors, which makes it possible to use raw user-supplied input for search, and can be used - with advanced operators. - - - `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery. - - `"quoted text"`: text inside quote marks will be converted to terms separated by <-> operators, as if processed by phraseto_tsquery. - - `OR`: the word “or” will be converted to the | operator. - - `-`: a dash will be converted to the ! operator. - - js: | - ```ts - const { data, error } = await supabase - .from('quotes') - .select('catchphrase') - .textSearch('catchphrase', `'fat or cat'`, { - type: 'websearch', - config: 'english' - }) - ``` - - match(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.match' - examples: - - name: With `select()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .match({ id: 2, name: 'Albania' }) - ``` - - - - - ```json - { - "data": [ - { - "name": "Albania" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .match({ id: 2, name: 'Albania' }) - ``` - - not(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.not' - notes: | - not() expects you to use the raw PostgREST syntax for the filter values. - - ```ts - .not('id', 'in', '(5,6,7)') // Use `()` for `in` filter - .not('arraycol', 'cs', '{"a","b"}') // Use `cs` for `contains()`, `{}` for array values - ``` - examples: - - name: With `select()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'null'), - (2, null); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .not('name', 'is', null) - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "null" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .not('name', 'is', null) - ``` - - or(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.or' - notes: | - or() expects you to use the raw PostgREST syntax for the filter names and values. - - ```ts - .or('id.in.(5,6,7), arraycol.cs.{"a","b"}') // Use `()` for `in` filter, `{}` for array values and `cs` for `contains()`. - .or('id.in.(5,6,7), arraycol.cd.{"a","b"}') // Use `cd` for `containedBy()` - ``` - examples: - - name: With `select()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .or('id.eq.2,name.eq.Algeria') - ``` - - - - - ```json - { - "data": [ - { - "name": "Albania" - }, - { - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .or('id.eq.2,name.eq.Algeria') - ``` - - name: Use `or` with `and` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .or('id.gt.3,and(id.eq.1,name.eq.Afghanistan)') - ``` - - - - - - ```json - { - "data": [ - { - "name": "Afghanistan" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - name: Use `or` on foreign tables - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'Germany'), - (2, 'Indonesia'); - insert into - cities (id, country_id, name) - values - (1, 2, 'Bali'), - (2, 1, 'Munich'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select(` - name, - cities!inner ( - name - ) - `) - .or('country_id.eq.1,name.eq.Beijing', { foreignTable: 'cities' }) - ``` - - - - - ```json - { - "data": [ - { - "name": "Germany", - "cities": [ - { - "name": "Munich" - } - ] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - filter(): - $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.filter' - notes: | - filter() expects you to use the raw PostgREST syntax for the filter values. - - ```ts - .filter('id', 'in', '(5,6,7)') // Use `()` for `in` filter - .filter('arraycol', 'cs', '{"a","b"}') // Use `cs` for `contains()`, `{}` for array values - ``` - examples: - - name: With `select()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .filter('name', 'in', '("Algeria","Japan")') - ``` - - - - - ```json - { - "data": [ - { - "id": 3, - "name": "Algeria" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .filter('name', 'in', '("Algeria","Japan")') - ``` - - name: On a foreign table - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'Germany'), - (2, 'Indonesia'); - insert into - cities (id, country_id, name) - values - (1, 2, 'Bali'), - (2, 1, 'Munich'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select(` - name, - cities!inner ( - name - ) - `) - .filter('cities.name', 'eq', 'Bali') - ``` - - - - - ```json - { - "data": [ - { - "name": "Indonesia", - "cities": [ - { - "name": "Bali" - } - ] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select(` - name, - cities!inner ( - name - ) - `) - .filter('cities.name', 'eq', 'Bali') - ``` - - Using Modifiers: - description: | - Filters work on the row level—they allows you to return rows that - only match certain conditions without changing the shape of the rows. - Modifiers are everything that don't fit that definition—allowing you to - change the format of the response (e.g., returning a CSV string). - - Modifiers must be specified after filters. Some modifiers only apply for - queries that return rows (e.g., `select()` or `rpc()` on a function that - returns a table response). - - db.modifiers.select(): - $ref: '@supabase/postgrest-js.PostgrestTransformBuilder.select' - title: select() - examples: - - name: With `upsert()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .upsert({ id: 1, name: 'Albania' }) - ``` - - - - - ```json - { - "status": 201, - "statusText": "Created" - } - ``` - - - - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .upsert({ id: 1, name: 'Algeria' }) - .select() - ``` - - - - - ```json - { - "data": [ - { - "id": 1, - "name": "Algeria" - } - ], - "status": 201, - "statusText": "Created" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .upsert({ id: 1, name: 'Algeria' }) - .select() - ``` - - order(): - $ref: '@supabase/postgrest-js.PostgrestTransformBuilder.order' - examples: - - name: With `select()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .order('name', { ascending: false }) - ``` - - - - - ```json - { - "data": [ - { - "id": 3, - "name": "Algeria" - }, - { - "id": 2, - "name": "Albania" - }, - { - "id": 1, - "name": "Afghanistan" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('cities') - .select('name', 'country_id') - .order('id', { ascending: false }) - ``` - - name: On a foreign table - description: | - Ordering on foreign tables doesn't affect the ordering of - the parent table. - - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'United States'), - (2, 'Vanuatu'); - insert into - cities (id, country_id, name) - values - (1, 1, 'Atlanta'), - (2, 1, 'New York City'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select(` - name, - cities ( - name - ) - `) - .order('name', { foreignTable: 'cities', ascending: false }) - ``` - - - - - ```json - { - "data": [ - { - "name": "United States", - "cities": [ - { - "name": "New York City" - }, - { - "name": "Atlanta" - } - ] - }, - { - "name": "Vanuatu", - "cities": [] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - limit(): - $ref: '@supabase/postgrest-js.PostgrestTransformBuilder.limit' - examples: - - name: With `select()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .limit(1) - ``` - - - - - ```json - { - "data": [ - { - "name": "Afghanistan" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .limit(1) - ``` - - name: On a foreign table - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - create table - cities ( - id int8 primary key, - country_id int8 not null references countries, - name text - ); - - insert into - countries (id, name) - values - (1, 'United States'); - insert into - cities (id, country_id, name) - values - (1, 1, 'Atlanta'), - (2, 1, 'New York City'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select(` - name, - cities ( - name - ) - `) - .limit(1, { foreignTable: 'cities' }) - ``` - - - - - ```json - { - "data": [ - { - "name": "United States", - "cities": [ - { - "name": "Atlanta" - } - ] - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - - range(): - $ref: '@supabase/postgrest-js.PostgrestTransformBuilder.range' - examples: - - name: With `select()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .range(0, 1) - ``` - - - - - ```json - { - "data": [ - { - "name": "Afghanistan" - }, - { - "name": "Albania" - } - ], - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .range(0, 1) - ``` - - db.abortSignal(): - $ref: '@supabase/postgrest-js.PostgrestTransformBuilder.abortSignal' - title: abortSignal() - examples: - - name: Aborting requests in-flight - description: | - You can use an [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to abort requests. Note that `status` and `statusText` don't mean anything for aborted requests as the request wasn't fulfilled. - - - - - ```ts - const ac = new AbortController() - ac.abort() - const { data, error } = await supabase - .from('very_big_table') - .select() - .abortSignal(ac.signal) - ``` - - - - - ```json - { - "error": { - "message": "FetchError: The user aborted a request.", - "details": "", - "hint": "", - "code": "" - }, - "status": 400, - "statusText": "Bad Request" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const ac = new AbortController() - ac.abort() - const { data, error } = await supabase - .from('very_big_table') - .select() - .abortSignal(ac.signal) - ``` - - single(): - $ref: '@supabase/postgrest-js.PostgrestTransformBuilder.single' - examples: - - name: With `select()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .limit(1) - .single() - ``` - - - - - ```json - { - "data": { - "name": "Afghanistan" - }, - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select('name') - .limit(1) - .single() - ``` - - maybeSingle(): - $ref: '@supabase/postgrest-js.PostgrestTransformBuilder.maybeSingle' - examples: - - name: With `select()` - description: | - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .eq('name', 'Singapore') - .maybeSingle() - ``` - - - - - ```json - { - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .eq('name', 'Singapore') - .maybeSingle() - ``` - - db.csv(): - $ref: '@supabase/postgrest-js.PostgrestTransformBuilder.csv' - title: csv() - examples: - - name: Return data as CSV - description: | - By default, the data is returned in JSON format, but can also be returned as Comma Separated Values. - - - - - ```sql - create table - countries (id int8 primary key, name text); - - insert into - countries (id, name) - values - (1, 'Afghanistan'), - (2, 'Albania'), - (3, 'Algeria'); - ``` - - - - - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .csv() - ``` - - - - - ```json - { - "data": "id,name\n1,Afghanistan\n2,Albania\n3,Algeria", - "status": 200, - "statusText": "OK" - } - ``` - - - - hideCodeBlock: true - isSpotlight: true - js: | - ```ts - const { data, error } = await supabase - .from('countries') - .select() - .csv() - ``` - - # NOTE: Not available on currently deployed PostgREST - # db.geojson(): - # $ref: '@supabase/postgrest-js.PostgrestTransformBuilder.geojson' - # title: geojson() - - # NOTE: Not available on currently deployed PostgREST - # db.explain(): - # $ref: '@supabase/postgrest-js.PostgrestTransformBuilder.explain' - # title: explain() - - # NOTE: Not available on currently deployed PostgREST - # db.rollback(): - # $ref: '@supabase/postgrest-js.PostgrestTransformBuilder.rollback' - # title: rollback() - # examples: - # - name: With `delete()` - # description: | - #
    - # Schema - - # ```sql - # create table - # countries (id int8 primary key, name text); - - # insert into - # countries (id, name) - # values - # (1, 'Spain'); - # ``` - #
    - - # ```ts - # const { error } = await supabase - # .from('countries') - # .delete() - # .eq('id', 1) - # .rollback() - # ``` - - #
    - # Result - - # ```json - # { - # "status": 204, - # "statusText": "No Content" - # } - # ``` - #
    - - # ```ts - # const { data, error } = await supabase - # .from('countries') - # .select() - # ``` - - #
    - # Result - - # ```json - # { - # "data": [ - # { - # "id": 1, - # "name": "Spain" - # } - # ], - # "status": 200, - # "statusText": "OK" - # } - # ``` - #
    - # hideCodeBlock: true - # isSpotlight: true - # js: | - # ```ts - # const { error } = await supabase - # .from('countries') - # .delete() - # .eq('id', 1) - # ``` - - invoke(): - title: 'invoke()' - description: | - Invoke a Supabase Function. - $ref: '@supabase/functions-js.FunctionsClient.invoke' - notes: | - - Requires an Authorization header. - - Invoke params generally match the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) spec. - - When you pass in a body to your function, we automatically attach the Content-Type header for `Blob`, `ArrayBuffer`, `File`, `FormData` and `String`. If it doesn't match any of these types we assume the payload is `json`, serialise it and attach the `Content-Type` header as `application/json`. You can override this behaviour by passing in a `Content-Type` header of your own. - - Responses are automatically parsed as `json`, `blob` and `form-data` depending on the `Content-Type` header sent by your function. Responses are parsed as `text` by default. - examples: - - name: Basic invocation. - description: - isSpotlight: true - js: | - ```js - const { data, error } = await supabase.functions.invoke('hello', { - body: { foo: 'bar' } - }) - ``` - - name: Error handling. - description: | - A `FunctionsHttpError` error is returned if your function throws an error, `FunctionsRelayError` if the Supabase Relay has an error processing your function and `FunctionsFetchError` if there is a network error in calling your function. - isSpotlight: true - js: | - ```js - import { FunctionsHttpError, FunctionsRelayError, FunctionsFetchError } from "@supabase/supabase-js"; - - const { data, error } = await supabase.functions.invoke('hello', { - headers: { - "my-custom-header": 'my-custom-header-value' - }, - body: { foo: 'bar' } - }) - - if (error instanceof FunctionsHttpError) { - console.log('Function returned an error', error.message) - } else if (error instanceof FunctionsRelayError) { - console.log('Relay error:', error.message) - } else if (error instanceof FunctionsFetchError) { - console.log('Fetch error:', error.message) - } - ``` - - name: Passing custom headers. - description: | - You can pass custom headers to your function. Note: supabase-js automatically passes the `Authorization` header with the signed in user's JWT. - isSpotlight: true - js: | - ```js - const { data, error } = await supabase.functions.invoke('hello', { - headers: { - "my-custom-header": 'my-custom-header-value' - }, - body: { foo: 'bar' } - }) - ``` - - subscribe(): - title: 'on().subscribe()' - $ref: '@supabase/realtime-js.RealtimeChannel.on' - notes: | - - Realtime is disabled by default for new Projects for better database performance and security. You can turn it on by [managing replication](/docs/guides/api#managing-realtime). - - Row level security is not applied to delete statements. - - If you want to receive the "previous" data for updates and deletes, you will need to set `REPLICA IDENTITY` to `FULL`, like this: `ALTER TABLE your_table REPLICA IDENTITY FULL;` - - When a delete occurs, the contents of old_record will be broadcast to all subscribers to that table so ensure that each table's replica identity only contains information that is safe to expose publicly. - - The channel name must exactly match the schema/table/filter you want to listen to separated by colons. See below examples for additional context. - examples: - - name: Listen to all database changes - isSpotlight: true - js: | - ```js - supabase - .channel('*') - .on('postgres_changes', { event: '*', schema: '*' }, payload => { - console.log('Change received!', payload) - }) - .subscribe() - ``` - - name: Listening to a specific table - js: | - ```js - supabase - .channel('public:countries') - .on('postgres_changes', { event: '*', schema: 'public', table: 'countries' }, payload => { - console.log('Change received!', payload) - }) - .subscribe() - ``` - - name: Listening to inserts - js: | - ```js - supabase - .channel('public:countries') - .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'countries' }, payload => { - console.log('Change received!', payload) - }) - .subscribe() - ``` - - name: Listening to updates - description: | - By default, Supabase will send only the updated record. If you want to receive the previous values as well you can - enable full replication for the table you are listening to: - - ```sql - alter table "your_table" replica identity full; - ``` - js: | - ```js - supabase - .channel('public:countries') - .on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'countries' }, payload => { - console.log('Change received!', payload) - }) - .subscribe() - ``` - - name: Listening to deletes - description: | - By default, Supabase does not send deleted records. If you want to receive the deleted record you can - enable full replication for the table you are listening too: - - ```sql - alter table "your_table" replica identity full; - ``` - js: | - ```js - supabase - .channel('public:countries') - .on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'countries' }, payload => { - console.log('Change received!', payload) - }) - .subscribe() - ``` - - name: Listening to multiple events - description: You can chain listeners if you want to listen to multiple events for each table. - js: | - ```js - supabase - .channel('public:countries') - .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'countries' }, handleRecordInserted) - .on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'countries' }, handleRecordDeleted) - .subscribe() - ``` - - name: Listening to row level changes - description: You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match. - notes: | - - ``eq`` filter works with all database types as under the hood, it's casting both the filter value and the database value to the correct type and then comparing them. - js: | - ```js - supabase - .channel('public:countries:id=eq.200') - .on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'countries', filter: 'id=eq.200' }, handleRecordUpdated) - .subscribe() - ``` - - getChannels(): - title: 'getChannels()' - $ref: '@supabase/supabase-js.index.SupabaseClient.getChannels' - examples: - - name: Get all channels - isSpotlight: true - js: | - ```js - const channels = supabase.getChannels() - ``` - - removeChannel(): - title: 'removeChannel()' - $ref: '@supabase/supabase-js.index.SupabaseClient.removeChannel' - notes: | - - Removing a channel is a great way to maintain the performance of your project's Realtime service as well as your database if you're listening to Postgres changes. Supabase will automatically handle cleanup 30 seconds after a client is disconnected, but unused channels may cause degradation as more clients are simultaneously subscribed. - examples: - - name: Removes a channel - isSpotlight: true - js: | - ```js - supabase.removeChannel(myChannel) - ``` - - removeAllChannels(): - title: 'removeAllChannels()' - $ref: '@supabase/supabase-js.index.SupabaseClient.removeAllChannels' - notes: | - - Removing channels is a great way to maintain the performance of your project's Realtime service as well as your database if you're listening to Postgres changes. Supabase will automatically handle cleanup 30 seconds after a client is disconnected, but unused channels may cause degradation as more clients are simultaneously subscribed. - examples: - - name: Remove all channels - isSpotlight: true - js: | - ```js - supabase.removeAllChannels() - ``` - - storage.listBuckets(): - title: 'listBuckets()' - $ref: '@supabase/storage-js.packages/StorageBucketApi.default.listBuckets' - notes: | - - RLS policy permissions required: - - `buckets` table permissions: `select` - - `objects` table permissions: none - - Refer to the [Storage guide](/docs/guides/storage#access-control) on how access control works - examples: - - name: List buckets - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .listBuckets() - ``` - - storage.getBucket(): - title: 'getBucket()' - $ref: '@supabase/storage-js.packages/StorageBucketApi.default.getBucket' - notes: | - - RLS policy permissions required: - - `buckets` table permissions: `select` - - `objects` table permissions: none - - Refer to the [Storage guide](/docs/guides/storage#access-control) on how access control works - examples: - - name: Get bucket - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .getBucket('avatars') - ``` - - storage.createBucket(): - title: 'createBucket()' - $ref: '@supabase/storage-js.packages/StorageBucketApi.default.createBucket' - notes: | - - RLS policy permissions required: - - `buckets` table permissions: `insert` - - `objects` table permissions: none - - Refer to the [Storage guide](/docs/guides/storage#access-control) on how access control works - examples: - - name: Create bucket - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .createBucket('avatars', { public: false }) - ``` - - storage.emptyBucket(): - title: 'emptyBucket()' - $ref: '@supabase/storage-js.packages/StorageBucketApi.default.emptyBucket' - notes: | - - RLS policy permissions required: - - `buckets` table permissions: `select` - - `objects` table permissions: `select` and `delete` - - Refer to the [Storage guide](/docs/guides/storage#access-control) on how access control works - examples: - - name: Empty bucket - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .emptyBucket('avatars') - ``` - storage.updateBucket(): - title: 'updateBucket()' - $ref: '@supabase/storage-js.packages/StorageBucketApi.default.updateBucket' - notes: | - - RLS policy permissions required: - - `buckets` table permissions: `update` - - `objects` table permissions: none - - Refer to the [Storage guide](/docs/guides/storage#access-control) on how access control works - examples: - - name: Update bucket - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .updateBucket('avatars', { public: false }) - ``` - - storage.deleteBucket(): - title: 'deleteBucket()' - $ref: '@supabase/storage-js.packages/StorageBucketApi.default.deleteBucket' - notes: | - - RLS policy permissions required: - - `buckets` table permissions: `select` and `delete` - - `objects` table permissions: none - - Refer to the [Storage guide](/docs/guides/storage#access-control) on how access control works - examples: - - name: Delete bucket - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .deleteBucket('avatars') - ``` - - storage.from.upload(): - title: 'from.upload()' - $ref: '@supabase/storage-js.packages/StorageFileApi.default.upload' - notes: | - - RLS policy permissions required: - - `buckets` table permissions: none - - `objects` table permissions: `insert` - - Refer to the [Storage guide](/docs/guides/storage#access-control) on how access control works - - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Upload file using `ArrayBuffer` from base64 file data instead, see example below. - examples: - - name: Upload file - isSpotlight: true - js: | - ```js - const avatarFile = event.target.files[0] - const { data, error } = await supabase - .storage - .from('avatars') - .upload('public/avatar1.png', avatarFile, { - cacheControl: '3600', - upsert: false - }) - ``` - - name: Upload file using `ArrayBuffer` from base64 file data - js: | - ```js - import { decode } from 'base64-arraybuffer' - - const { data, error } = await supabase - .storage - .from('avatars') - .upload('public/avatar1.png', decode('base64FileData'), { - contentType: 'image/png' - }) - ``` - - storage.from.update(): - title: 'from.update()' - $ref: '@supabase/storage-js.packages/StorageFileApi.default.update' - notes: | - - RLS policy permissions required: - - `buckets` table permissions: none - - `objects` table permissions: `update` and `select` - - Refer to the [Storage guide](/docs/guides/storage#access-control) on how access control works - - For React Native, using either `Blob`, `File` or `FormData` does not work as intended. Update file using `ArrayBuffer` from base64 file data instead, see example below. - examples: - - name: Update file - isSpotlight: true - js: | - ```js - const avatarFile = event.target.files[0] - const { data, error } = await supabase - .storage - .from('avatars') - .update('public/avatar1.png', avatarFile, { - cacheControl: '3600', - upsert: false - }) - ``` - - name: Update file using `ArrayBuffer` from base64 file data - js: | - ```js - import {decode} from 'base64-arraybuffer' - - const { data, error } = await supabase - .storage - .from('avatars') - .update('public/avatar1.png', decode('base64FileData'), { - contentType: 'image/png' - }) - ``` - - storage.from.move(): - title: 'from.move()' - $ref: '@supabase/storage-js.packages/StorageFileApi.default.move' - notes: | - - RLS policy permissions required: - - `buckets` table permissions: none - - `objects` table permissions: `update` and `select` - - Refer to the [Storage guide](/docs/guides/storage#access-control) on how access control works - examples: - - name: Move file - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .from('avatars') - .move('public/avatar1.png', 'private/avatar2.png') - ``` - - storage.from.copy(): - title: 'from.copy()' - $ref: '@supabase/storage-js.packages/StorageFileApi.default.copy' - notes: | - - RLS policy permissions required: - - `buckets` table permissions: none - - `objects` table permissions: `insert` and `select` - - Refer to the [Storage guide](/docs/guides/storage#access-control) on how access control works - examples: - - name: Copy file - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .from('avatars') - .copy('public/avatar1.png', 'private/avatar2.png') - ``` - - storage.from.createSignedUrl(): - title: 'from.createSignedUrl()' - $ref: '@supabase/storage-js.packages/StorageFileApi.default.createSignedUrl' - notes: | - - RLS policy permissions required: - - `buckets` table permissions: none - - `objects` table permissions: `select` - - Refer to the [Storage guide](/docs/guides/storage#access-control) on how access control works - examples: - - name: Create Signed URL - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .from('avatars') - .createSignedUrl('folder/avatar1.png', 60) - ``` - - storage.from.createSignedUrls(): - title: 'from.createSignedUrls()' - $ref: '@supabase/storage-js.packages/StorageFileApi.default.createSignedUrls' - notes: | - - RLS policy permissions required: - - `buckets` table permissions: none - - `objects` table permissions: `select` - - Refer to the [Storage guide](/docs/guides/storage#access-control) on how access control works - examples: - - name: Create Signed URLs - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .from('avatars') - .createSignedUrls(['folder/avatar1.png', 'folder/avatar2.png'], 60) - ``` - - storage.from.getPublicUrl(): - title: 'from.getPublicUrl()' - $ref: '@supabase/storage-js.packages/StorageFileApi.default.getPublicUrl' - notes: | - - The bucket needs to be set to public, either via [updateBucket()](/docs/reference/javascript/storage-updatebucket) or by going to Storage on [app.supabase.com](https://app.supabase.com), clicking the overflow menu on a bucket and choosing "Make public" - - RLS policy permissions required: - - `buckets` table permissions: none - - `objects` table permissions: none - - Refer to the [Storage guide](/docs/guides/storage#access-control) on how access control works - examples: - - name: Returns the URL for an asset in a public bucket - isSpotlight: true - js: | - ```js - const { data } = supabase - .storage - .from('public-bucket') - .getPublicUrl('folder/avatar1.png') - ``` - - storage.from.download(): - title: 'from.download()' - $ref: '@supabase/storage-js.packages/StorageFileApi.default.download' - notes: | - - RLS policy permissions required: - - `buckets` table permissions: none - - `objects` table permissions: `select` - - Refer to the [Storage guide](/docs/guides/storage#access-control) on how access control works - examples: - - name: Download file - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .from('avatars') - .download('folder/avatar1.png') - ``` - - storage.from.remove(): - title: 'from.remove()' - $ref: '@supabase/storage-js.packages/StorageFileApi.default.remove' - notes: | - - RLS policy permissions required: - - `buckets` table permissions: none - - `objects` table permissions: `delete` and `select` - - Refer to the [Storage guide](/docs/guides/storage#access-control) on how access control works - examples: - - name: Delete file - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .from('avatars') - .remove(['folder/avatar1.png']) - ``` - - storage.from.list(): - title: 'from.list()' - $ref: '@supabase/storage-js.packages/StorageFileApi.default.list' - notes: | - - RLS policy permissions required: - - `buckets` table permissions: none - - `objects` table permissions: `select` - - Refer to the [Storage guide](/docs/guides/storage#access-control) on how access control works - examples: - - name: List files in a bucket - isSpotlight: true - js: | - ```js - const { data, error } = await supabase - .storage - .from('avatars') - .list('folder', { - limit: 100, - offset: 0, - sortBy: { column: 'name', order: 'asc' }, - }) - ``` - - name: Search files in a bucket - js: | - ```js - const { data, error } = await supabase - .storage - .from('avatars') - .list('folder', { - limit: 100, - offset: 0, - sortBy: { column: 'name', order: 'asc' }, - search: 'jon' - }) - ``` From 9c1a39a1ef80fad1e0d4150c244371248fc9e23f Mon Sep 17 00:00:00 2001 From: dannykng Date: Wed, 21 Dec 2022 14:19:27 -0800 Subject: [PATCH 141/215] Restore cli_v1_commands--old.yaml --- spec/cli_v1_commands--old.yaml | 882 +++++++++++++++++++++++++++++++++ 1 file changed, 882 insertions(+) create mode 100644 spec/cli_v1_commands--old.yaml diff --git a/spec/cli_v1_commands--old.yaml b/spec/cli_v1_commands--old.yaml new file mode 100644 index 00000000000..d7af310d127 --- /dev/null +++ b/spec/cli_v1_commands--old.yaml @@ -0,0 +1,882 @@ +clispec: '001' +info: + id: cli + version: 1.11.3 + title: Supabase CLI + language: sh + source: https://github.com/supabase/cli + bugs: https://github.com/supabase/cli/issues + spec: https://github.com/supabase/supabase.tools/cli_spec/lib.yaml + description: | + Supabase CLI provides you with tools to develop your application locally, and deploy your application to the Supabase platform. + options: |- + ```sh + --debug output debug logs to stderr + --experimental enable experimental features + -h, --help help for supabase + --workdir string path to a Supabase project directory + ``` + tags: + - id: local-dev + title: Local Development + - id: management-api + title: Management APIs + - id: other-commands + title: Additional Commands +commands: + - id: supabase-test + title: supabase test + summary: Run tests on local Supabase containers + tags: + - local-dev + links: [] + subcommands: + - supabase-test-db + options: |- + ```sh + -h, --help help for test + ``` + - id: supabase-test-db + title: supabase test db + summary: Tests local database with pgTAP + tags: [] + links: [] + usage: |- + ```sh + supabase test db [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for db + ``` + - id: supabase-stop + title: supabase stop + summary: Stop all local Supabase containers + tags: + - local-dev + links: [] + usage: |- + ```sh + supabase stop [flags] + ``` + subcommands: [] + options: |- + ```sh + --backup Backs up the current database before stopping. + -h, --help help for stop + ``` + - id: supabase-status + title: supabase status + summary: Show status of local Supabase containers + tags: + - local-dev + links: [] + usage: |- + ```sh + supabase status [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for status + ``` + - id: supabase-start + title: supabase start + summary: Start containers for Supabase local development + tags: + - local-dev + links: [] + usage: |- + ```sh + supabase start [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for start + ``` + - id: supabase-secrets + title: supabase secrets + summary: Manage Supabase secrets + tags: + - management-api + links: [] + subcommands: + - supabase-secrets-list + - supabase-secrets-set + - supabase-secrets-unset + options: |- + ```sh + -h, --help help for secrets + ``` + - id: supabase-secrets-unset + title: supabase secrets unset + summary: Unset a secret(s) on Supabase + description: Unset a secret(s) from the linked Supabase project. + tags: [] + links: [] + usage: |- + ```sh + supabase secrets unset ... [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for unset + ``` + - id: supabase-secrets-set + title: supabase secrets set + summary: Set a secret(s) on Supabase + description: Set a secret(s) to the linked Supabase project. + tags: [] + links: [] + usage: |- + ```sh + supabase secrets set [flags] ... + ``` + subcommands: [] + options: |- + ```sh + --env-file string Read secrets from a .env file. + -h, --help help for set + ``` + - id: supabase-secrets-list + title: supabase secrets list + summary: List all secrets on Supabase + description: List all secrets in the linked project. + tags: [] + links: [] + usage: |- + ```sh + supabase secrets list [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for list + ``` + - id: supabase-projects + title: supabase projects + summary: Manage Supabase projects + tags: + - management-api + links: [] + subcommands: + - supabase-projects-create + - supabase-projects-list + options: |- + ```sh + -h, --help help for projects + ``` + - id: supabase-projects-list + title: supabase projects list + summary: List all Supabase projects + description: List all Supabase projects the logged-in user can access. + tags: [] + links: [] + usage: |- + ```sh + supabase projects list [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for list + ``` + - id: supabase-projects-create + title: supabase projects create + summary: Create a project on Supabase + tags: [] + links: [] + usage: |- + ```sh + supabase projects create [flags] + ``` + subcommands: [] + options: |- + ```sh + --db-password string Database password of the project. + -h, --help help for create + -i, --interactive Enables interactive mode. + --org-id string Organization ID to create the project in. + --plan string Select a plan that suits your needs. (default "free") + --region string Select a region close to you for the best performance. + ``` + - id: supabase-orgs + title: supabase orgs + summary: Manage Supabase organizations + tags: + - management-api + links: [] + subcommands: + - supabase-orgs-list + options: |- + ```sh + -h, --help help for orgs + ``` + - id: supabase-orgs-list + title: supabase orgs list + summary: List all organizations + description: List all organizations the logged-in user belongs. + tags: [] + links: [] + usage: |- + ```sh + supabase orgs list [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for list + ``` + - id: supabase-migration + title: supabase migration + summary: Manage database migration scripts + tags: + - local-dev + links: [] + subcommands: + - supabase-migration-list + - supabase-migration-new + options: |- + ```sh + -h, --help help for migration + ``` + - id: supabase-migration-new + title: supabase migration new + summary: Create an empty migration script + tags: [] + links: [] + usage: |- + ```sh + supabase migration new [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for new + ``` + - id: supabase-migration-list + title: supabase migration list + summary: List local and remote migrations + tags: [] + links: [] + usage: |- + ```sh + supabase migration list [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for list + -p, --password string Password to your remote Postgres database. + ``` + - id: supabase-login + title: supabase login + summary: Authenticate using an access token + tags: + - local-dev + links: [] + usage: |- + ```sh + supabase login [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for login + ``` + - id: supabase-link + title: supabase link + summary: Link to a Supabase project + tags: + - local-dev + links: [] + usage: |- + ```sh + supabase link [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for link + -p, --password string Password to your remote Postgres database. + --project-ref string Project ref of the Supabase project. + ``` + - id: supabase-init + title: supabase init + summary: Initialize a local project + tags: + - local-dev + links: [] + usage: |- + ```sh + supabase init [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for init + ``` + - id: supabase-gen + title: supabase gen + summary: Run code generation tools + tags: + - local-dev + links: [] + subcommands: + - supabase-gen-types + options: |- + ```sh + -h, --help help for gen + ``` + - id: supabase-gen-types + title: supabase gen types + summary: Generate types from Postgres schema + tags: [] + links: [] + subcommands: + - supabase-gen-types-typescript + options: |- + ```sh + -h, --help help for types + ``` + - id: supabase-gen-types-typescript + title: supabase gen types typescript + summary: Generate types for TypeScript + description: | + Generate types for TypeScript. Must specify one of --local, --linked, --project-id, or --db-url + tags: [] + links: [] + usage: |- + ```sh + supabase gen types typescript [flags] + ``` + subcommands: [] + options: |- + ```sh + --db-url string Generate types from a database url. + -h, --help help for typescript + --linked Generate types from the linked project. + --local Generate types from the local dev database. + --project-id string Generate types from a project ID. + --schema stringArray Schemas to generate types for. + ``` + - id: supabase-functions + title: supabase functions + summary: Manage Supabase Edge functions + tags: + - management-api + links: [] + subcommands: + - supabase-functions-delete + - supabase-functions-deploy + - supabase-functions-new + - supabase-functions-serve + options: |- + ```sh + -h, --help help for functions + ``` + - id: supabase-functions-serve + title: supabase functions serve + summary: Serve a Function locally + tags: [] + links: [] + usage: |- + ```sh + supabase functions serve [flags] + ``` + subcommands: [] + options: |- + ```sh + --env-file string Path to an env file to be populated to the Function environment. + -h, --help help for serve + --no-verify-jwt Disable JWT verification for the Function. + ``` + - id: supabase-functions-new + title: supabase functions new + summary: Create a new Function locally + tags: [] + links: [] + usage: |- + ```sh + supabase functions new [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for new + ``` + - id: supabase-functions-deploy + title: supabase functions deploy + summary: Deploy a Function to Supabase + description: Deploy a Function to the linked Supabase project. + tags: [] + links: [] + usage: |- + ```sh + supabase functions deploy [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for deploy + --no-verify-jwt Disable JWT verification for the Function. + --project-ref string Project ref of the Supabase project. + ``` + - id: supabase-functions-delete + title: supabase functions delete + summary: Delete a Function from Supabase + description: | + Delete a Function from the linked Supabase project. This does NOT remove the Function locally. + tags: [] + links: [] + usage: |- + ```sh + supabase functions delete [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for delete + --project-ref string Project ref of the Supabase project. + ``` + - id: supabase-domains + title: supabase domains + summary: Manage custom domain names for Supabase projects + tags: + - management-api + links: [] + subcommands: + - supabase-domains-activate + - supabase-domains-create + - supabase-domains-delete + - supabase-domains-get + - supabase-domains-reverify + options: |- + ```sh + -h, --help help for domains + --include-raw-output Include raw output (useful for debugging). + --project-ref string Project ref of the Supabase project. + ``` + - id: supabase-domains-reverify + title: supabase domains reverify + summary: Re-verify the custom hostname config for your project. + tags: [] + links: [] + usage: |- + ```sh + supabase domains reverify [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for reverify + ``` + - id: supabase-domains-get + title: supabase domains get + summary: Get the current custom hostname config. + description: | + Retrieve the custom hostname config for your project, as stored in the Supabase platform. + tags: [] + links: [] + usage: |- + ```sh + supabase domains get [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for get + ``` + - id: supabase-domains-delete + title: supabase domains delete + summary: Deletes the custom hostname config for your project. + tags: [] + links: [] + usage: |- + ```sh + supabase domains delete [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for delete + ``` + - id: supabase-domains-create + title: supabase domains create + summary: Create a custom hostname. + description: |- + Create a custom hostname for your Supabase project. + + Expects your custom hostname to have a CNAME record to your Supabase project's subdomain. + tags: [] + links: [] + usage: |- + ```sh + supabase domains create [flags] + ``` + subcommands: [] + options: |- + ```sh + --custom-hostname string The custom hostname to use for your Supabase project. + -h, --help help for create + ``` + - id: supabase-domains-activate + title: supabase domains activate + summary: Activate the custom hostname for a project. + description: |- + Activates the custom hostname configuration for a project. + + This reconfigures your Supabase project to respond to requests on your custom hostname. + After the custom hostname is activated, your project's auth services will no longer function on the Supabase-provisioned subdomain. + tags: [] + links: [] + usage: |- + ```sh + supabase domains activate [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for activate + ``` + - id: supabase-db + title: supabase db + summary: Manage local Postgres databases + tags: + - local-dev + links: [] + subcommands: + - supabase-db-branch + - supabase-db-diff + - supabase-db-lint + - supabase-db-push + - supabase-db-remote + - supabase-db-reset + options: |- + ```sh + -h, --help help for db + ``` + - id: supabase-db-reset + title: supabase db reset + summary: Resets the local database to current migrations + tags: [] + links: [] + usage: |- + ```sh + supabase db reset [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for reset + ``` + - id: supabase-db-remote + title: supabase db remote + summary: Manage remote databases + tags: [] + links: [] + subcommands: + - supabase-db-remote-changes + - supabase-db-remote-commit + options: |- + ```sh + -h, --help help for remote + -p, --password string Password to your remote Postgres database. + ``` + - id: supabase-db-remote-commit + title: supabase db remote commit + summary: Commit remote changes as a new migration + tags: [] + links: [] + usage: |- + ```sh + supabase db remote commit [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for commit + ``` + - id: supabase-db-remote-changes + title: supabase db remote changes + summary: Show changes on the remote database + description: Show changes on the remote database since last migration. + tags: [] + links: [] + usage: |- + ```sh + supabase db remote changes [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for changes + ``` + - id: supabase-db-push + title: supabase db push + summary: Push new migrations to the remote database + tags: [] + links: [] + usage: |- + ```sh + supabase db push [flags] + ``` + subcommands: [] + options: |- + ```sh + --dry-run Print the migrations that would be applied, but don't actually apply them. + -h, --help help for push + -p, --password string Password to your remote Postgres database. + ``` + - id: supabase-db-lint + title: supabase db lint + summary: Checks local database for typing error + tags: [] + links: [] + usage: |- + ```sh + supabase db lint [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for lint + --level string Error level to emit. (default "warning") + -s, --schema strings List of schema to include. (default [public]) + ``` + - id: supabase-db-diff + title: supabase db diff + summary: Diffs the local database for schema changes + tags: [] + links: [] + usage: |- + ```sh + supabase db diff [flags] + ``` + subcommands: [] + options: |- + ```sh + -f, --file string Saves schema diff to a file. + -h, --help help for diff + --linked Diffs local schema against linked project. + -s, --schema strings List of schema to include. (default [public]) + --use-migra Use migra to generate schema diff. + ``` + - id: supabase-db-branch + title: supabase db branch + summary: Manage local database branches + description: | + Manage local database branches. Each branch is associated with a separate local database. Forking remote databases is NOT supported. + tags: [] + links: [] + subcommands: + - supabase-db-branch-create + - supabase-db-branch-delete + - supabase-db-branch-list + - supabase-db-branch-switch + options: |- + ```sh + -h, --help help for branch + ``` + - id: supabase-db-branch-switch + title: supabase db branch switch + summary: Switch the active branch + tags: [] + links: [] + usage: |- + ```sh + supabase db branch switch [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for switch + ``` + - id: supabase-db-branch-list + title: supabase db branch list + summary: List branches + tags: [] + links: [] + usage: |- + ```sh + supabase db branch list [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for list + ``` + - id: supabase-db-branch-delete + title: supabase db branch delete + summary: Delete a branch + tags: [] + links: [] + usage: |- + ```sh + supabase db branch delete [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for delete + ``` + - id: supabase-db-branch-create + title: supabase db branch create + summary: Create a branch + tags: [] + links: [] + usage: |- + ```sh + supabase db branch create [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for create + ``` + - id: supabase-completion + title: supabase completion + summary: Generate the autocompletion script for the specified shell + description: | + Generate the autocompletion script for supabase for the specified shell. + See each sub-command's help for details on how to use the generated script. + tags: + - other-commands + links: [] + subcommands: + - supabase-completion-bash + - supabase-completion-fish + - supabase-completion-powershell + - supabase-completion-zsh + options: |- + ```sh + -h, --help help for completion + ``` + - id: supabase-completion-zsh + title: supabase completion zsh + summary: Generate the autocompletion script for zsh + description: | + Generate the autocompletion script for the zsh shell. + + If shell completion is not already enabled in your environment you will need + to enable it. You can execute the following once: + + echo "autoload -U compinit; compinit" >> ~/.zshrc + + To load completions in your current shell session: + + source <(supabase completion zsh); compdef _supabase supabase + + To load completions for every new session, execute once: + + #### Linux: + + supabase completion zsh > "${fpath[1]}/_supabase" + + #### macOS: + + supabase completion zsh > $(brew --prefix)/share/zsh/site-functions/_supabase + + You will need to start a new shell for this setup to take effect. + tags: [] + links: [] + usage: |- + ```sh + supabase completion zsh [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for zsh + --no-descriptions disable completion descriptions + ``` + - id: supabase-completion-powershell + title: supabase completion powershell + summary: Generate the autocompletion script for powershell + description: | + Generate the autocompletion script for powershell. + + To load completions in your current shell session: + + supabase completion powershell | Out-String | Invoke-Expression + + To load completions for every new session, add the output of the above command + to your powershell profile. + tags: [] + links: [] + usage: |- + ```sh + supabase completion powershell [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for powershell + --no-descriptions disable completion descriptions + ``` + - id: supabase-completion-fish + title: supabase completion fish + summary: Generate the autocompletion script for fish + description: | + Generate the autocompletion script for the fish shell. + + To load completions in your current shell session: + + supabase completion fish | source + + To load completions for every new session, execute once: + + supabase completion fish > ~/.config/fish/completions/supabase.fish + + You will need to start a new shell for this setup to take effect. + tags: [] + links: [] + usage: |- + ```sh + supabase completion fish [flags] + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for fish + --no-descriptions disable completion descriptions + ``` + - id: supabase-completion-bash + title: supabase completion bash + summary: Generate the autocompletion script for bash + description: | + Generate the autocompletion script for the bash shell. + + This script depends on the 'bash-completion' package. + If it is not installed already, you can install it via your OS's package manager. + + To load completions in your current shell session: + + source <(supabase completion bash) + + To load completions for every new session, execute once: + + #### Linux: + + supabase completion bash > /etc/bash_completion.d/supabase + + #### macOS: + + supabase completion bash > $(brew --prefix)/etc/bash_completion.d/supabase + + You will need to start a new shell for this setup to take effect. + tags: [] + links: [] + usage: |- + ```sh + supabase completion bash + ``` + subcommands: [] + options: |- + ```sh + -h, --help help for bash + --no-descriptions disable completion descriptions + ``` From aacfc1dee63ce07c3665775f380c166629561d97 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 21 Dec 2022 21:12:04 -0330 Subject: [PATCH 142/215] Fix ReactMarkdown expects a string error --- .../reference/RefFunctionSection.tsx | 6 +--- spec/supabase_js_v2.yml | 36 +++++++++---------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/apps/docs/components/reference/RefFunctionSection.tsx b/apps/docs/components/reference/RefFunctionSection.tsx index 32002c389bc..a796a7b4aa9 100644 --- a/apps/docs/components/reference/RefFunctionSection.tsx +++ b/apps/docs/components/reference/RefFunctionSection.tsx @@ -60,11 +60,7 @@ const RefFunctionSection: React.FC = (props) => { {item.description}
    )} - {/* {functionMarkdownContent && ( -
    - -
    - )} */} + {item.notes && (
    {item.notes} diff --git a/spec/supabase_js_v2.yml b/spec/supabase_js_v2.yml index 309fe559544..237b0a3b729 100644 --- a/spec/supabase_js_v2.yml +++ b/spec/supabase_js_v2.yml @@ -401,7 +401,7 @@ functions: isSpotlight: true code: | ```js - const { data, error } = supabase.auth.setSession({ + const { data, error } = supabase.auth.setSession({ access_token, refresh_token ) @@ -433,7 +433,7 @@ functions: $ref: '@supabase/gotrue-js.GoTrueClient.onAuthStateChange' notes: | - Types of auth events: `SIGNED_IN`, `SIGNED_OUT`, `TOKEN_REFRESHED`, `USER_UPDATED`, `PASSWORD_RECOVERY` - - Currently, `onAuthStateChange()` does not work across tabs. + - Currently, `onAuthStateChange()` does not work across tabs. For instance, in the case of a password reset flow, the original tab which requested for the password reset link will not receive the `SIGNED_IN` and `PASSWORD_RECOVERY` event when the user clicks on the link. examples: @@ -453,7 +453,7 @@ functions: supabase.auth.onAuthStateChange((event, session) => { if (event == 'PASSWORD_RECOVERY') { console.log('PASSWORD_RECOVERY', session) - + // show screen to update user's password showPasswordResetScreen(true) } @@ -509,8 +509,8 @@ functions: isSpotlight: true code: | ```js - const { data, error } = await supabase.auth.mfa.enroll({ - factorType: 'totp' + const { data, error } = await supabase.auth.mfa.enroll({ + factorType: 'totp' }) // Use the id to create a challenge. @@ -530,7 +530,7 @@ functions: isSpotlight: true code: | ```js - const { data, error } = await supabase.auth.mfa.challenge({ + const { data, error } = await supabase.auth.mfa.challenge({ factorId: '34e770dd-9ff9-416c-87fa-43b31d7ef225' }) ``` @@ -545,9 +545,9 @@ functions: isSpotlight: true code: | ```js - const { data, error } = await supabase.auth.mfa.verify({ + const { data, error } = await supabase.auth.mfa.verify({ factorId: '34e770dd-9ff9-416c-87fa-43b31d7ef225', - challengeId: '4034ae6f-a8ce-4fb5-8ee5-69a5863a7c15', + challengeId: '4034ae6f-a8ce-4fb5-8ee5-69a5863a7c15', code: '123456' }) ``` @@ -565,7 +565,7 @@ functions: ```js const { data, error } = await supabase.auth.mfa.challengeAndVerify({ factorId: '34e770dd-9ff9-416c-87fa-43b31d7ef225', - challengeId: '4034ae6f-a8ce-4fb5-8ee5-69a5863a7c15', + challengeId: '4034ae6f-a8ce-4fb5-8ee5-69a5863a7c15', code: '123456' }) ``` @@ -729,18 +729,18 @@ functions: title: 'resetPasswordForEmail()' $ref: '@supabase/gotrue-js.GoTrueClient.resetPasswordForEmail' notes: | - - The password reset flow consist of 2 broad steps: (i) Allow the user to login via the password reset link; (ii) Update the user's password. - - The `resetPasswordForEmail()` only sends a password reset link to the user's email. + - The password reset flow consist of 2 broad steps: (i) Allow the user to login via the password reset link; (ii) Update the user's password. + - The `resetPasswordForEmail()` only sends a password reset link to the user's email. To update the user's password, see [`updateUser()`](/docs/reference/javascript/auth-updateuser). - - A `SIGNED_IN` and `PASSWORD_RECOVERY` event will be emitted when the password recovery link is clicked. + - A `SIGNED_IN` and `PASSWORD_RECOVERY` event will be emitted when the password recovery link is clicked. You can use [`onAuthStateChange()`](/docs/reference/javascript/auth-onauthstatechange) to listen and invoke a callback function on these events. - When the user clicks the reset link in the email they are redirected back to your application. - You can configure the URL that the user is redirected to with the `redirectTo` parameter. + You can configure the URL that the user is redirected to with the `redirectTo` parameter. See [redirect URLs and wildcards](/docs/guides/auth/overview#redirect-urls-and-wildcards) to add additional redirect URLs to your project. - After the user has been redirected successfully, prompt them for a new password and call `updateUser()`: ```js - const { data, error } = await supabase.auth.updateUser({ - password: new_password + const { data, error } = await supabase.auth.updateUser({ + password: new_password }) ``` examples: @@ -934,10 +934,10 @@ functions: isSpotlight: true code: | ```js - const { data, error } = await supabase.auth.admin.mfa.deleteFactor({ + const { data, error } = await supabase.auth.admin.mfa.deleteFactor({ id: '34e770dd-9ff9-416c-87fa-43b31d7ef225', userId: 'a89baba7-b1b7-440f-b4bb-91026967f66b', - }) + }) ``` - id: select title: 'Fetch data: select()' @@ -5201,7 +5201,7 @@ functions: - id: text-search title: textSearch() $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.textSearch' - notes: + notes: | - For more information, see [Postgres full text search](/docs/guides/database/full-text-search). examples: - id: text-search From 5a3e10e67e915ebd3cd3deab9520d18427193d58 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 21 Dec 2022 21:31:05 -0330 Subject: [PATCH 143/215] Fix missing image warnings --- .../NavigationMenu.constants.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index b5b21af6420..a7c634f9ba6 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -14,7 +14,7 @@ export const REFERENCES: References = { name: 'supabase-js', library: 'supabase-js', versions: ['v2', 'v1'], - icon: '/docs/img/libraries/javascript-icon.svg', + icon: '/img/libraries/javascript-icon', }, dart: { name: 'Flutter', @@ -688,22 +688,22 @@ export const reference = { url: '/reference/javascript/start', level: 'reference_javascript', items: [], - icon: 'docs/img/icons/javascript.svg', + icon: '/img/icons/menu/reference-javascript', }, { name: 'supabase-dart', url: '/reference/dart/start', level: 'reference_dart', items: [], - icon: 'docs/img/icons/javascript.svg', - }, - { - name: 'supbase-python', - url: '/reference/python/start', - level: 'reference_python', - items: [], - icon: 'docs/img/icons/javascript.svg', + icon: '/img/icons/menu/reference-dart', }, + // { + // name: 'supbase-python', + // url: '/reference/python/start', + // level: 'reference_python', + // items: [], + // icon: '/img/icons/menu/reference-javascript', + // }, ], }, { @@ -713,13 +713,13 @@ export const reference = { name: 'Supabase CLI', url: '/reference/cli/start', items: [], - icon: 'docs/img/icons/cli-icon.svg', + icon: '/img/icons/menu/reference-cli', }, { name: 'Management API', url: '/reference/javascript', items: [], - icon: 'docs/img/icons/javascript-icon.svg', + icon: '/img/icons/menu/reference-api', }, ], }, From c02d0b5074a6981add58fded7d811719ea87f425 Mon Sep 17 00:00:00 2001 From: dng Date: Wed, 21 Dec 2022 17:18:16 -0800 Subject: [PATCH 144/215] Fix typo --- .../Navigation/NavigationMenu/NavigationMenu.constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index a7c634f9ba6..d9837f2ea65 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -698,7 +698,7 @@ export const reference = { icon: '/img/icons/menu/reference-dart', }, // { - // name: 'supbase-python', + // name: 'supabase-python', // url: '/reference/python/start', // level: 'reference_python', // items: [], From df5305eb7c51870b905a185bdcd11c16e481407c Mon Sep 17 00:00:00 2001 From: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com> Date: Wed, 21 Dec 2022 18:07:05 -0800 Subject: [PATCH 145/215] Add missing import to component On line 138 the component is using a `useState` function call which is a react hook, however no react hook is imported causing an error. --- apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx index df4d8171a4d..0a174410282 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx @@ -125,6 +125,7 @@ export default MyApp Wrap your `pages/_app.tsx` component with the `SessionContextProvider` component: ```jsx lines=2,8 title=pages/_app.tsx +import { useState } from 'react' import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' import { SessionContextProvider, Session } from '@supabase/auth-helpers-react' From 6298d5179b1b36ce3ca4e146d41d7c0135833317 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Wed, 21 Dec 2022 21:25:13 -0500 Subject: [PATCH 146/215] init og image generator --- supabase/.gitignore | 3 + supabase/config.toml | 71 +++++++++++++++++++ .../functions/og-images/component/Docs.tsx | 56 +++++++++++++++ supabase/functions/og-images/handler.tsx | 45 ++++++++++++ supabase/functions/og-images/index.ts | 4 ++ supabase/seed.sql | 0 6 files changed, 179 insertions(+) create mode 100644 supabase/.gitignore create mode 100644 supabase/config.toml create mode 100644 supabase/functions/og-images/component/Docs.tsx create mode 100644 supabase/functions/og-images/handler.tsx create mode 100644 supabase/functions/og-images/index.ts create mode 100644 supabase/seed.sql diff --git a/supabase/.gitignore b/supabase/.gitignore new file mode 100644 index 00000000000..773c7c3e0a1 --- /dev/null +++ b/supabase/.gitignore @@ -0,0 +1,3 @@ +# Supabase +.branches +.temp diff --git a/supabase/config.toml b/supabase/config.toml new file mode 100644 index 00000000000..74e1c6daa07 --- /dev/null +++ b/supabase/config.toml @@ -0,0 +1,71 @@ +# A string used to distinguish different Supabase projects on the same host. Defaults to the working +# directory name when running `supabase init`. +project_id = "supabase" + +[api] +# Port to use for the API URL. +port = 54321 +# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API +# endpoints. public and storage are always included. +schemas = [] +# Extra schemas to add to the search_path of every request. +extra_search_path = ["extensions"] +# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size +# for accidental or malicious requests. +max_rows = 1000 + +[db] +# Port to use for the local database URL. +port = 54322 +# The database major version to use. This has to be the same as your remote database's. Run `SHOW +# server_version;` on the remote database to check. +major_version = 15 + +[studio] +# Port to use for Supabase Studio. +port = 54323 + +# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they +# are monitored, and you can view the emails that would have been sent from the web interface. +[inbucket] +# Port to use for the email testing server web interface. +port = 54324 +smtp_port = 54325 +pop3_port = 54326 + +[storage] +# The maximum file size allowed (e.g. "5MB", "500KB"). +file_size_limit = "50MiB" + +[auth] +# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used +# in emails. +site_url = "http://localhost:3000" +# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. +additional_redirect_urls = ["https://localhost:3000"] +# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one +# week). +jwt_expiry = 3600 +# Allow/disallow new user signups to your project. +enable_signup = true + +[auth.email] +# Allow/disallow new user signups via email to your project. +enable_signup = true +# If enabled, a user will be required to confirm any email change on both the old, and new email +# addresses. If disabled, only the new email is required to confirm. +double_confirm_changes = true +# If enabled, users need to confirm their email address before signing in. +enable_confirmations = false + +# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, +# `discord`, `facebook`, `github`, `gitlab`, `google`, `twitch`, `twitter`, `slack`, `spotify`. +[auth.external.apple] +enabled = false +client_id = "" +secret = "" +# Overrides the default auth redirectUrl. +redirect_uri = "" +# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, +# or any other third-party OIDC providers. +url = "" diff --git a/supabase/functions/og-images/component/Docs.tsx b/supabase/functions/og-images/component/Docs.tsx new file mode 100644 index 00000000000..3bd4dd65d27 --- /dev/null +++ b/supabase/functions/og-images/component/Docs.tsx @@ -0,0 +1,56 @@ +import React from 'https://esm.sh/react@18.2.0?deno-std=0.140.0' + +type Props = { + type?: string | null + title: string + description: string + icon?: string | null +} + +const Docs = (props: Props) => { + const { type, title, description, icon } = props + + return ( +
    +
    + {icon && ( + + )} + {type && icon && ( +
    + )} + {type && ( + <> +
    + +
    + {type} + + )} +
    +
    +
    +

    {title}

    +

    {description}

    +
    +
    + + DOCS +
    +
    +
    + ) +} + +export default Docs \ No newline at end of file diff --git a/supabase/functions/og-images/handler.tsx b/supabase/functions/og-images/handler.tsx new file mode 100644 index 00000000000..85dfc4cd6e2 --- /dev/null +++ b/supabase/functions/og-images/handler.tsx @@ -0,0 +1,45 @@ +import React from 'https://esm.sh/react@18.2.0?deno-std=0.140.0' +import { ImageResponse } from 'https://deno.land/x/og_edge@0.0.4/mod.ts' +import Docs from './component/Docs.tsx' + +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', + 'Content-Type': 'application/json' +} + +const options = { + width: 1200, + height: 600, + status: 200, + headers: { + 'content-type': 'image/png', + 'cache-control': 'public, max-age=31536000, s-maxage=31536000, no-transform, immutable', + 'cdn-cache-control': 'max-age=31536000', + }, +} + +export async function handler(req: Request) { + const url = new URL(req.url) + const site = url.searchParams.get('site')?.toLowerCase() ?? url.searchParams.get('amp;site')?.toLowerCase() + const icon = url.searchParams.get('icon')?.toLowerCase() ?? url.searchParams.get('amp;icon')?.toLowerCase() + const type = url.searchParams.get('type') ?? url.searchParams.get('amp;type') + const title = url.searchParams.get('title') ?? url.searchParams.get('amp;title') + const description = url.searchParams.get('description') ?? url.searchParams.get('amp;description') + + if(!site || !title || !description) { + return new Response(JSON.stringify({ message: 'missing params' }), { + headers: { ...corsHeaders }, + status: 404, + }) + } + + if (site === 'docs') { + return new ImageResponse(( ), { ...options }) + } + + return new Response(JSON.stringify({error: 'Internal server error'}), { + headers: { ...corsHeaders }, + status: 500 + }) +} diff --git a/supabase/functions/og-images/index.ts b/supabase/functions/og-images/index.ts new file mode 100644 index 00000000000..ebf53f34dce --- /dev/null +++ b/supabase/functions/og-images/index.ts @@ -0,0 +1,4 @@ +import { serve } from 'https://deno.land/std@0.170.0/http/server.ts' +import { handler } from './handler.tsx' + +serve(handler) diff --git a/supabase/seed.sql b/supabase/seed.sql new file mode 100644 index 00000000000..e69de29bb2d From 41a1d98cc25c7a6dda02d342d8fdf9a92417a4ee Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Wed, 21 Dec 2022 21:47:03 -0500 Subject: [PATCH 147/215] Refactor to handle multiple sites --- supabase/functions/og-images/handler.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/supabase/functions/og-images/handler.tsx b/supabase/functions/og-images/handler.tsx index 85dfc4cd6e2..642bc6a94c3 100644 --- a/supabase/functions/og-images/handler.tsx +++ b/supabase/functions/og-images/handler.tsx @@ -34,12 +34,16 @@ export async function handler(req: Request) { }) } - if (site === 'docs') { - return new ImageResponse(( ), { ...options }) + switch (site) { + case 'docs': + return new ImageResponse(( ), { ...options }) + break; + + default: + return new Response(JSON.stringify({message: 'site not found'}), { + headers: { ...corsHeaders }, + status: 404, + }) + break; } - - return new Response(JSON.stringify({error: 'Internal server error'}), { - headers: { ...corsHeaders }, - status: 500 - }) } From 7651d306f090abfa2aeb390c0a0fce808a1bd69b Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Wed, 21 Dec 2022 22:11:46 -0500 Subject: [PATCH 148/215] init readme --- supabase/functions/og-images/README.md | 44 ++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 supabase/functions/og-images/README.md diff --git a/supabase/functions/og-images/README.md b/supabase/functions/og-images/README.md new file mode 100644 index 00000000000..38095508e4b --- /dev/null +++ b/supabase/functions/og-images/README.md @@ -0,0 +1,44 @@ +# Open Graph (OG) Image Generator + +Generate OG Images for the sites at supabase. + +## How to use + +To use the og image generator, you will need to visit the site with the following parameters: + +`site`: a string indicating which site you want to generate an image for. Currently, the only available option is docs. + +`title`: a string representing the title of the image you want to generate. + +`description`: a string representing the description of the image you want to generate. + +`type`: a string indicating the type of image you want to generate. This is optional and is only used for the docs site. + +`icon`: a string indicating the icon you want to use for the image. This is optional and is only used for the docs site. + +If any of these required parameters are missing, you will receive a 404 response with a message indicating that some parameters are missing. + +If the site parameter is not recognized, you will receive a 404 response with a message indicating that the site was not found. + +If all required parameters are provided and the site is recognized, you will receive an image in the form of a PNG file with the specified title, description, and other optional parameters included. The image will have a width of 1200 pixels and a height of 600 pixels. + +Here is an example link that you can use to test the website: + +https://www.example.com/image-generator?site=docs&title=Example%20Title&description=Example%20Description&type=Auth&icon=google + +This link will generate an image for the docs site with the title "Example Title", the description "Example Description", the Auth type, and the Google icon. + +## Run locally + +First we need to start supabase using the [supabase cli](https://supabase.com/docs/reference/cli/introduction) + +```bash +supabase start +``` + +Then run the function +```bash +supabase functions serve og-images +``` + +Now we can visit [localhost:54321/functions/v1/og-images/?site=docs&title=Title&description=Description&type=Auth](http://localhost:54321/functions/v1/og-images/?site=docs&title=Title&description=Description&type=Auth) to see your changes localy. \ No newline at end of file From e6a3e34ac965722de89e28196b23169296ff6ea2 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Thu, 22 Dec 2022 11:57:55 +0800 Subject: [PATCH 149/215] fix mobile nav --- .../NavigationMenu/NavigationMenu.tsx | 2 +- apps/docs/layouts/SiteLayout.tsx | 41 ++++--------------- 2 files changed, 10 insertions(+), 33 deletions(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx index 9e99d968290..e4052fa881e 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx @@ -181,7 +181,7 @@ const SideNav = () => { const isReference_Self_Hosting_Realtime = 'reference_self_hosting_realtime' === level return ( -
    +
    {/* // main menu */} diff --git a/apps/docs/layouts/SiteLayout.tsx b/apps/docs/layouts/SiteLayout.tsx index 8d8a116c7ad..8a9aa181028 100644 --- a/apps/docs/layouts/SiteLayout.tsx +++ b/apps/docs/layouts/SiteLayout.tsx @@ -1,15 +1,14 @@ import { useTheme } from 'common/Providers' import Image from 'next/image' import Link from 'next/link' -import { useRouter } from 'next/router' import NavigationMenu from '~/components/Navigation/NavigationMenu/NavigationMenu' import TopNavBarRef from '~/components/Navigation/NavigationMenu/TopNavBarRef' import FooterHelpCallout from '~/components/FooterHelpCallout' -import { menuState, useMenuLevelId, useMenuMobileOpen } from '~/hooks/useMenuState' import { memo, useEffect } from 'react' import Footer from '~/components/Navigation/Footer' +import { menuState, useMenuLevelId, useMenuMobileOpen } from '~/hooks/useMenuState' const levelsData = { home: { @@ -163,43 +162,21 @@ const MobileMenuBackdrop = memo(function MobileMenuBackdrop() { return (
    menuState.setMenuMobileOpen(!mobileMenuOpen)} >
    ) }) -// const SideMenu = memo(function SideMenu() { -// const mobileMenuOpen = useMenuMobileOpen() - -// return ( -//
    -// -//
    -// ) -// }) - const SideMenu = memo(function SideMenu() { - const mobileMenuOpen = useMenuMobileOpen() - return (
    Date: Wed, 21 Dec 2022 23:02:51 -0500 Subject: [PATCH 150/215] use custom font --- .../functions/og-images/component/Docs.tsx | 1 + supabase/functions/og-images/handler.tsx | 32 ++++++++++++------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/supabase/functions/og-images/component/Docs.tsx b/supabase/functions/og-images/component/Docs.tsx index 3bd4dd65d27..eef40786d56 100644 --- a/supabase/functions/og-images/component/Docs.tsx +++ b/supabase/functions/og-images/component/Docs.tsx @@ -19,6 +19,7 @@ const Docs = (props: Props) => { flexDirection: 'column', color: 'white', backgroundColor: '#1c1c1c', + fontFamily: 'Circular', backgroundImage: 'url(https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/images/og-images/gradient.svg)', backgroundRepeat: 'no-repeat', }} diff --git a/supabase/functions/og-images/handler.tsx b/supabase/functions/og-images/handler.tsx index 642bc6a94c3..1e7b9ae98bf 100644 --- a/supabase/functions/og-images/handler.tsx +++ b/supabase/functions/og-images/handler.tsx @@ -8,16 +8,10 @@ const corsHeaders = { 'Content-Type': 'application/json' } -const options = { - width: 1200, - height: 600, - status: 200, - headers: { - 'content-type': 'image/png', - 'cache-control': 'public, max-age=31536000, s-maxage=31536000, no-transform, immutable', - 'cdn-cache-control': 'max-age=31536000', - }, -} +// Load custom font +const FONT_URL = 'https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/images/lw6/CircularStd-Book.otf' +const font = fetch(new URL(FONT_URL, import.meta.url)).then((res) => res.arrayBuffer()) +const fontData = await font export async function handler(req: Request) { const url = new URL(req.url) @@ -36,7 +30,23 @@ export async function handler(req: Request) { switch (site) { case 'docs': - return new ImageResponse(( ), { ...options }) + return new ImageResponse(( ), + { + width: 1200, + height: 630, + fonts: [ + { + name: 'Circular', + data: fontData, + style: 'normal', + }, + ], + headers: { + 'content-type': 'image/png', + 'cache-control': 'public, max-age=31536000, s-maxage=31536000, no-transform, immutable', + 'cdn-cache-control': 'max-age=31536000', + }, + }) break; default: From d939fa988bf26b4b559637c216121a883913aecb Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Thu, 22 Dec 2022 12:06:13 +0800 Subject: [PATCH 151/215] fix padding --- apps/docs/layouts/SiteLayout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/layouts/SiteLayout.tsx b/apps/docs/layouts/SiteLayout.tsx index 8a9aa181028..9aa8c37d9c0 100644 --- a/apps/docs/layouts/SiteLayout.tsx +++ b/apps/docs/layouts/SiteLayout.tsx @@ -186,7 +186,7 @@ const SideMenu = memo(function SideMenu() { 'top-[0px]', 'bg-scale-200', // desktop styles - 'lg:relative lg:top-0 lg:left-0 lg:pb-10 lg:px-10 lg:pt-8 lg:flex', + 'lg:relative lg:top-0 lg:left-0 lg:pb-10 lg:px-10 lg:pt-0 lg:flex', 'lg:opacity-100 lg:visible', ].join(' ')} > From c4e37be8c3adac278162cf0af6a6308a8031805b Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Thu, 22 Dec 2022 12:20:41 +0800 Subject: [PATCH 152/215] fix linkn --- .../Navigation/NavigationMenu/NavigationMenuRefListItems.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx index dc309823ad7..fd87508d468 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx @@ -296,7 +296,7 @@ const Content: React.FC = ({ id, lib, commonSections, sp // handle normal links return ( <> - + {fn.items && fn.items //.filter((item) => item.libs.includes(lib)) From 8410965410d4ba7ad8331c0e4bf31d3b38d21b85 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Thu, 22 Dec 2022 13:27:38 +0800 Subject: [PATCH 153/215] Update NavigationMenu.tsx --- .../components/Navigation/NavigationMenu/NavigationMenu.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx index e4052fa881e..ad9a190f55b 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx @@ -167,6 +167,7 @@ const SideNav = () => { const isStorageActive = 'storage' === level const isPlatformActive = 'platform' === level const isResourcesActive = 'resources' === level + const isSelfHosting = 'self_hosting' === level const isIntegrationsActive = 'integrations' === level const isReferenceActive = 'reference' === level @@ -192,6 +193,7 @@ const SideNav = () => { + {/* // Client Libs */} From bc0a03bbf60482010b5d64289a0763971a95c42a Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Thu, 22 Dec 2022 00:33:17 -0500 Subject: [PATCH 154/215] move docs to og generator --- apps/docs/layouts/guides/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/docs/layouts/guides/index.tsx b/apps/docs/layouts/guides/index.tsx index e3b64828a60..14a5d08ba33 100644 --- a/apps/docs/layouts/guides/index.tsx +++ b/apps/docs/layouts/guides/index.tsx @@ -60,6 +60,8 @@ const Layout: FC = (props) => { const hasTableOfContents = tocList.length > 0 + const ogPageType = asPath.split('/')[2] + return ( <> = (props) => { url: `https://supabase.com/docs${asPath}`, images: [ { - url: `https://supabase.com/docs/img/supabase-og-image.png`, + url: `https://obuldanrptloktxcffvn.functions.supabase.co/og-images?site=docs${ogPageType ? `&type=${ogPageType}` : ''}&title=${props.meta?.title}&description=${props.meta?.description}`, }, ], }} From 63fc5826690b304705527109249dd90289fdf362 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Thu, 22 Dec 2022 00:37:10 -0500 Subject: [PATCH 155/215] run prettier --- apps/docs/layouts/guides/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/docs/layouts/guides/index.tsx b/apps/docs/layouts/guides/index.tsx index 14a5d08ba33..991183239d9 100644 --- a/apps/docs/layouts/guides/index.tsx +++ b/apps/docs/layouts/guides/index.tsx @@ -73,7 +73,9 @@ const Layout: FC = (props) => { url: `https://supabase.com/docs${asPath}`, images: [ { - url: `https://obuldanrptloktxcffvn.functions.supabase.co/og-images?site=docs${ogPageType ? `&type=${ogPageType}` : ''}&title=${props.meta?.title}&description=${props.meta?.description}`, + url: `https://obuldanrptloktxcffvn.functions.supabase.co/og-images?site=docs${ + ogPageType ? `&type=${ogPageType}` : '' + }&title=${props.meta?.title}&description=${props.meta?.description}`, }, ], }} From 7515608d01b681c08504e997dcb4b36b852b7cf5 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Thu, 22 Dec 2022 00:41:28 -0500 Subject: [PATCH 156/215] remove supabase cli init --- supabase/config.toml | 71 -------------------------------------------- supabase/seed.sql | 0 2 files changed, 71 deletions(-) delete mode 100644 supabase/config.toml delete mode 100644 supabase/seed.sql diff --git a/supabase/config.toml b/supabase/config.toml deleted file mode 100644 index 74e1c6daa07..00000000000 --- a/supabase/config.toml +++ /dev/null @@ -1,71 +0,0 @@ -# A string used to distinguish different Supabase projects on the same host. Defaults to the working -# directory name when running `supabase init`. -project_id = "supabase" - -[api] -# Port to use for the API URL. -port = 54321 -# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API -# endpoints. public and storage are always included. -schemas = [] -# Extra schemas to add to the search_path of every request. -extra_search_path = ["extensions"] -# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size -# for accidental or malicious requests. -max_rows = 1000 - -[db] -# Port to use for the local database URL. -port = 54322 -# The database major version to use. This has to be the same as your remote database's. Run `SHOW -# server_version;` on the remote database to check. -major_version = 15 - -[studio] -# Port to use for Supabase Studio. -port = 54323 - -# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they -# are monitored, and you can view the emails that would have been sent from the web interface. -[inbucket] -# Port to use for the email testing server web interface. -port = 54324 -smtp_port = 54325 -pop3_port = 54326 - -[storage] -# The maximum file size allowed (e.g. "5MB", "500KB"). -file_size_limit = "50MiB" - -[auth] -# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used -# in emails. -site_url = "http://localhost:3000" -# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. -additional_redirect_urls = ["https://localhost:3000"] -# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one -# week). -jwt_expiry = 3600 -# Allow/disallow new user signups to your project. -enable_signup = true - -[auth.email] -# Allow/disallow new user signups via email to your project. -enable_signup = true -# If enabled, a user will be required to confirm any email change on both the old, and new email -# addresses. If disabled, only the new email is required to confirm. -double_confirm_changes = true -# If enabled, users need to confirm their email address before signing in. -enable_confirmations = false - -# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, -# `discord`, `facebook`, `github`, `gitlab`, `google`, `twitch`, `twitter`, `slack`, `spotify`. -[auth.external.apple] -enabled = false -client_id = "" -secret = "" -# Overrides the default auth redirectUrl. -redirect_uri = "" -# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, -# or any other third-party OIDC providers. -url = "" diff --git a/supabase/seed.sql b/supabase/seed.sql deleted file mode 100644 index e69de29bb2d..00000000000 From 6d7461271d8ecb1acd494503fddeec88d388af69 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Thu, 22 Dec 2022 00:55:45 -0500 Subject: [PATCH 157/215] update og image readme --- supabase/functions/og-images/README.md | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/supabase/functions/og-images/README.md b/supabase/functions/og-images/README.md index 38095508e4b..886b9f69b9e 100644 --- a/supabase/functions/og-images/README.md +++ b/supabase/functions/og-images/README.md @@ -6,31 +6,27 @@ Generate OG Images for the sites at supabase. To use the og image generator, you will need to visit the site with the following parameters: -`site`: a string indicating which site you want to generate an image for. Currently, the only available option is docs. +`site`: indicating which site you want to generate an image for. Currently, the only available option is docs. -`title`: a string representing the title of the image you want to generate. +`title`: representing the title of the image you want to generate. -`description`: a string representing the description of the image you want to generate. +`description`: representing the description of the image you want to generate. -`type`: a string indicating the type of image you want to generate. This is optional and is only used for the docs site. +`type` (optional): indicating the type of image you want to generate. This is optional and is only used for the docs site. -`icon`: a string indicating the icon you want to use for the image. This is optional and is only used for the docs site. +`icon` (optional): indicating the icon you want to use for the image. This is optional and is only used for the docs site. -If any of these required parameters are missing, you will receive a 404 response with a message indicating that some parameters are missing. - -If the site parameter is not recognized, you will receive a 404 response with a message indicating that the site was not found. - -If all required parameters are provided and the site is recognized, you will receive an image in the form of a PNG file with the specified title, description, and other optional parameters included. The image will have a width of 1200 pixels and a height of 600 pixels. +If any of the required parameters are missing, you will receive a 404 response with a message indicating that some parameters are missing. If the site parameter is not recognized, you will receive a 404 response with a message indicating that the site was not found. Here is an example link that you can use to test the website: -https://www.example.com/image-generator?site=docs&title=Example%20Title&description=Example%20Description&type=Auth&icon=google +https://obuldanrptloktxcffvn.functions.supabase.co/og-images?site=docs&title=Example%20Title&description=Example%20Description&type=Auth&icon=google This link will generate an image for the docs site with the title "Example Title", the description "Example Description", the Auth type, and the Google icon. ## Run locally -First we need to start supabase using the [supabase cli](https://supabase.com/docs/reference/cli/introduction) +First we need to start up supabase using the [supabase cli](https://supabase.com/docs/reference/cli/introduction) ```bash supabase start @@ -41,4 +37,4 @@ Then run the function supabase functions serve og-images ``` -Now we can visit [localhost:54321/functions/v1/og-images/?site=docs&title=Title&description=Description&type=Auth](http://localhost:54321/functions/v1/og-images/?site=docs&title=Title&description=Description&type=Auth) to see your changes localy. \ No newline at end of file +Now we can visit [localhost:54321/functions/v1/og-images/?site=docs&title=Title&description=Description&type=Auth](http://localhost:54321/functions/v1/og-images/?site=docs&title=Title&description=Description&type=Auth) to see your changes localy. From de3287e47f8e9a0edc02901103741072552c6146 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Thu, 22 Dec 2022 01:03:31 -0500 Subject: [PATCH 158/215] encode the title and description --- apps/docs/layouts/guides/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/layouts/guides/index.tsx b/apps/docs/layouts/guides/index.tsx index 991183239d9..0c79c49e05c 100644 --- a/apps/docs/layouts/guides/index.tsx +++ b/apps/docs/layouts/guides/index.tsx @@ -75,7 +75,7 @@ const Layout: FC = (props) => { { url: `https://obuldanrptloktxcffvn.functions.supabase.co/og-images?site=docs${ ogPageType ? `&type=${ogPageType}` : '' - }&title=${props.meta?.title}&description=${props.meta?.description}`, + }&title=${encodeURIComponent(props.meta?.title)}&description=${encodeURIComponent(props.meta?.description)}`, }, ], }} From d24b3ba0c40f486f7d8c9478bcf5830a5652b932 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Thu, 22 Dec 2022 01:04:17 -0500 Subject: [PATCH 159/215] run prettier --- apps/docs/layouts/guides/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/docs/layouts/guides/index.tsx b/apps/docs/layouts/guides/index.tsx index 0c79c49e05c..07ec7929195 100644 --- a/apps/docs/layouts/guides/index.tsx +++ b/apps/docs/layouts/guides/index.tsx @@ -75,7 +75,9 @@ const Layout: FC = (props) => { { url: `https://obuldanrptloktxcffvn.functions.supabase.co/og-images?site=docs${ ogPageType ? `&type=${ogPageType}` : '' - }&title=${encodeURIComponent(props.meta?.title)}&description=${encodeURIComponent(props.meta?.description)}`, + }&title=${encodeURIComponent(props.meta?.title)}&description=${encodeURIComponent( + props.meta?.description + )}`, }, ], }} From a6b6e6bf2bbf24f2c908a1147266e9d57ac6d703 Mon Sep 17 00:00:00 2001 From: egor-romanov <58992960+egor-romanov@users.noreply.github.com> Date: Thu, 22 Dec 2022 09:08:57 +0300 Subject: [PATCH 160/215] add step using "shadow databases" to prisma guide (#11169) * add step using "shadow databases" to prisma guide *Co-authored-by: dng --- .../docs/pages/guides/integrations/prisma.mdx | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/apps/docs/pages/guides/integrations/prisma.mdx b/apps/docs/pages/guides/integrations/prisma.mdx index 7334621d910..6a80e95fee6 100644 --- a/apps/docs/pages/guides/integrations/prisma.mdx +++ b/apps/docs/pages/guides/integrations/prisma.mdx @@ -63,21 +63,38 @@ This project comes with TypeScript configured and has the following structure. ### Configuring the project to use PostgreSQL -Go ahead and delete the `prisma/dev.db` file because we will be switching to PostgreSQL. -Next, inside the `prisma/.env` file, update the value of the `DATABASE_URL` variable to the connection string you got in **step 1**. The URL might look as follows: +By default, Prisma migrations will try to drop the `postgres` database, which can lead to conflicts with Supabase databases. For this scenario, use [Prisma Shadow Databases](https://www.prisma.io/docs/concepts/components/prisma-migrate/shadow-database#cloud-hosted-shadow-databases-must-be-created-manually). -```env -# prisma/.env -postgres://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.co:5432/postgres +Create a shadow database in your PostgreSQL server within the same Supabase project using the `psql` CLI and the `DATABASE_URL` from the previous steps (or use the local database). + +```bash +psql postgresql://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.red:5432 ``` -Finally, inside your `schema.prisma` file, change the `provider` from “sqlite” to `“postgresql”`. +After you connect to your project's PostgreSQL instance, create another database (e.g., `postgres_shadow`): + +```bash +postgres=> CREATE DATABASE postgres_shadow; +postgres=> exit +``` + +Go ahead and delete the `prisma/dev.db` file because we will be switching to PostgreSQL. +In the `.env` file, update `DATABASE_URL` and `SHADOW_DATABASE_URL` to the connection string from **step 1**. The `.env` file should look like: + +```env +# .env +DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.co:5432/postgres" +SHADOW_DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.co:5432/postgres_shadow" +``` + +In the `schema.prisma` file, change the `provider` from "sqlite" to `"postgresql"` and add the `shadowDatabaseUrl` property. This is what your `schema.prisma` file should look like: ```go datasource db { provider = “postgresql” url = env(“DATABASE_URL”) + shadowDatabaseUrl = env("SHADOW_DATABASE_URL") } generator client { provider = “prisma-client-js” @@ -124,11 +141,12 @@ This will create a `prisma/migrations` folder inside your `prisma` directory and If you’re working in a serverless environment (for example Node.js functions hosted on AWS Lambda, Vercel or Netlify Functions), you need to set up [connection pooling](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#serverless-environments-faas) using a tool like [PgBouncer](https://www.pgbouncer.org/). That’s because every function invocation may result in a [new connection to the database](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#the-serverless-challenge). Supabase [supports connection management using PgBouncer](https://supabase.io/blog/2021/04/02/supabase-pgbouncer#what-is-connection-pooling) and are enabled by default. Go to the **Database** page from the sidebar in the Supabase dashboard and navigate to **connection pool** settings ![Connection pool settings](/docs/img/guides/integrations/prisma/w0oowg8vq435ob5c3gf0.png) -When running migrations you need to use the non pooled connection URL (like the one we used in **step 1**). However, when deploying your app, you’ll use the pooled connection URL and add the `?pgbouncer=true` flag to the PostgreSQL connection URL. To minimize the number of concurrent connections, setting the `connection_limit` to `1` is also recommended. So the URL might look as follows: +When migrating, you need to use the non-pooled connection URL (like the one used in **step 1**). However, when deploying your app, use the pooled connection URL and add the `?pgbouncer=true` flag to the PostgreSQL connection URL. It's also recommended to minimize the number of concurrent connections by setting the `connection_limit` to `1`. The `.env` file should look like: ```env -# prisma/.env -postgres://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.co:6543/postgres?pgbouncer=true&connection_limit=1 +# .env +DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.co:6543/postgres?pgbouncer=true&connection_limit=1" +SHADOW_DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.co:5432/postgres_shadow" ``` Prisma Migrate uses database transactions to check out the current state of the database and the migrations table. However, the Migration Engine is designed to use a single connection to the database, and does not support connection pooling with PgBouncer. If you attempt to run Prisma Migrate commands in any environment that uses PgBouncer for connection pooling, you might see the following error: From c5901320d096895bc7532db09371355d881c7055 Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Thu, 22 Dec 2022 16:35:32 +0800 Subject: [PATCH 161/215] Update index.mdx --- apps/docs/pages/index.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/pages/index.mdx b/apps/docs/pages/index.mdx index 9921286f50c..17bc30954ec 100644 --- a/apps/docs/pages/index.mdx +++ b/apps/docs/pages/index.mdx @@ -11,9 +11,9 @@ export const meta = {

    Supabase is an open source Firebase alternative providing all the backend features you need to - build a product. [Learn more](/docs/architecture) about Supabase, start with the - [tutorials](/docs/guides/getting-started#tutorials), or dive straight into the different products - and APIs. + build a product. [Learn more](/docs/guides/getting-started/architecture) about Supabase, start + with the [tutorials](/docs/guides/getting-started#tutorials), or dive straight into the different + products and APIs.

    {/* start container */} From 94e97a455217d87fa8de2223fd0a752fb815c8af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Thu, 22 Dec 2022 13:17:23 +0100 Subject: [PATCH 162/215] chore: do not send captcha token for subscription endpoint (#11200) --- studio/components/interfaces/Billing/Billing.utils.ts | 6 ++---- studio/components/interfaces/Billing/EnterpriseUpdate.tsx | 6 ++---- .../components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx | 5 ++--- studio/components/interfaces/Billing/ProUpgrade.tsx | 6 ++---- 4 files changed, 8 insertions(+), 15 deletions(-) diff --git a/studio/components/interfaces/Billing/Billing.utils.ts b/studio/components/interfaces/Billing/Billing.utils.ts index 67f98f93e7b..4bad6ed40a7 100644 --- a/studio/components/interfaces/Billing/Billing.utils.ts +++ b/studio/components/interfaces/Billing/Billing.utils.ts @@ -52,8 +52,7 @@ export const formSubscriptionUpdatePayload = ( }, nonChangeableAddons: SubscriptionAddon[], selectedPaymentMethod: string, - region: string, - hcaptchaToken: string | undefined + region: string ) => { const { computeSize, pitrDuration, customDomains } = selectedAddons @@ -78,8 +77,7 @@ export const formSubscriptionUpdatePayload = ( ...(tierPriceId && { tier: tierPriceId.id }), addons, proration_date, - payment_method: selectedPaymentMethod, - hcaptchaToken + payment_method: selectedPaymentMethod } } diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index 254d83275c7..969b5d58993 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -114,8 +114,7 @@ const EnterpriseUpdate: FC = ({ selectedAddons, nonChangeableAddons, selectedPaymentMethodId, - projectRegion, - undefined + projectRegion ), tier: currentSubscription.tier.price_id, } @@ -178,8 +177,7 @@ const EnterpriseUpdate: FC = ({ selectedAddons, nonChangeableAddons, selectedPaymentMethodId, - projectRegion, - token ?? undefined + projectRegion ), tier: currentSubscription.tier.price_id, } diff --git a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx index 439f5edc6f3..9cda2b5aa30 100644 --- a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx +++ b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx @@ -109,7 +109,7 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { setMessage(values.message) return setShowConfirmModal(true) } else { - downgradeProject(values, token as string) + downgradeProject(values) } } @@ -118,7 +118,7 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { captchaRef.current?.resetCaptcha() } - const downgradeProject = async (values?: any, hcaptchaToken?: string) => { + const downgradeProject = async (values?: any) => { const downgradeMessage = values?.message ?? message try { @@ -132,7 +132,6 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { tier, addons, proration_date, - hcaptchaToken: captchaToken ?? hcaptchaToken, }) resetCaptcha() diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index 2f05f9a8844..b4cf2635051 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -137,8 +137,7 @@ const ProUpgrade: FC = ({ selectedAddons, nonChangeableAddons, selectedPaymentMethodId, - projectRegion, - undefined + projectRegion ) setIsRefreshingPreview(true) @@ -184,8 +183,7 @@ const ProUpgrade: FC = ({ selectedAddons, nonChangeableAddons, selectedPaymentMethodId, - projectRegion, - captchaToken ?? undefined + projectRegion ) const res = await patch(`${API_URL}/projects/${projectRef}/subscription`, payload) resetCaptcha() From b58c99189fcaff23c657207440a780ae9170f0ed Mon Sep 17 00:00:00 2001 From: Francisco Mazzoni Date: Thu, 22 Dec 2022 09:42:19 -0300 Subject: [PATCH 163/215] after lw6 first pass --- apps/www/pages/launch-week/index.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/www/pages/launch-week/index.tsx b/apps/www/pages/launch-week/index.tsx index 7888948b8c7..3fc5fb1d0ff 100644 --- a/apps/www/pages/launch-week/index.tsx +++ b/apps/www/pages/launch-week/index.tsx @@ -1030,7 +1030,7 @@ export default function launchweek() {
    - Happening all week + Submissions Closed

    Launch Week Hackathon

    @@ -1212,8 +1212,14 @@ export default function launchweek() { })}

    - - Currently Happening + + Shipped

    The Supabase Content Storm

    From 7d27671e4971b7f9ce244acefdf3ec1b8c389840 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Thu, 22 Dec 2022 10:07:59 -0330 Subject: [PATCH 164/215] More console warnings --- .../NavigationMenu/NavigationMenu.tsx | 2 +- .../NavigationMenuGuideListItems.tsx | 16 ++- .../NavigationMenuRefListItems.tsx | 116 ++++++++---------- apps/docs/pages/guides/functions.mdx | 2 +- 4 files changed, 63 insertions(+), 73 deletions(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx index 8017146442b..cfb4d87b143 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx @@ -62,7 +62,7 @@ export type RefKeyOptions = | 'self-hosting-storage' | 'self-hosting-realtime' -const NavigationMenu = ({ context }) => { +const NavigationMenu = () => { const router = useRouter() let version = '' diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideListItems.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideListItems.tsx index f298683984d..e4e73a8e132 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideListItems.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideListItems.tsx @@ -137,7 +137,14 @@ const ContentLink = React.memo(function ContentLink(props: any) { : 'hover:text-brand-900 text-scale-1000', ].join(' ')} > - {props.icon && } + {props.icon && ( + {props.icon} + )} {props.name} @@ -177,14 +184,15 @@ const Content = (props) => { - {menu.items.map((x, index) => { + {menu.items.map((x) => { return ( -

    +
    {x.items && x.items.length > 0 ? (
    {x.items.map((subItem, subItemIndex) => { return ( { })}
    ) : ( - + )}
    ) diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx index fd87508d468..87a90b82baa 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuRefListItems.tsx @@ -12,8 +12,9 @@ import RevVersionDropdown from '~/components/RefVersionDropdown' import { useMenuActiveRefId } from '~/hooks/useMenuState' import { RefIdOptions, RefKeyOptions } from './NavigationMenu' -import React from 'react' +import React, { Fragment } from 'react' import { generateAllowedClientLibKeys } from '~/lib/refGenerator/helpers' +import { isFuncNotInLibraryOrVersion } from './NavigationMenu.utils' const HeaderImage = React.memo(function HeaderImage(props: any) { const router = useRouter() @@ -131,6 +132,7 @@ const RenderLink = React.memo(function RenderLink(props: any) { .map((item) => { return ( = ({ id, lib, commonSections, sp
      {sections.map((fn: any, fnIndex) => { - // - // check if the link is allowed to be displayed - function isFuncNotInLibraryOrVersion(id, type) { - if (id && allowedKeys && !allowedKeys.includes(id) && type !== 'markdown') { - /* - * Remove this menu link from side bar, as it does not exist for either - * this language, or for this lib version - * - * */ - return true - } else { - return false - } - } - // run allow check - if (isFuncNotInLibraryOrVersion(fn.id, fn.type)) { - return <> + if (isFuncNotInLibraryOrVersion(fn.id, fn.type, allowedKeys)) { + return } // handle subtitles with subitems - if (!fn.id) { - return ( - <> - - - {fn.items && - fn.items - //.filter((item) => item.libs && item.libs.includes(lib)) - .map((item) => { - // run allow check - if (isFuncNotInLibraryOrVersion(item.id, item.type)) return <> - return ( - - ) - })} - - ) - } else { - // handle normal links - return ( - <> - - {fn.items && - fn.items - //.filter((item) => item.libs.includes(lib)) - .map((item) => ( - - ))} - - ) - } + return fn.id ? ( + + + {fn.items && + fn.items + //.filter((item) => item.libs.includes(lib)) + .map((item) => ( + + ))} + + ) : ( + + + + {fn.items && + fn.items.map((item, i) => { + // run allow check + if (isFuncNotInLibraryOrVersion(item.id, item.type, allowedKeys)) + return + return ( + + ) + })} + + ) })}
    diff --git a/apps/docs/pages/guides/functions.mdx b/apps/docs/pages/guides/functions.mdx index fdceb0ceea9..9c970cb6bfe 100644 --- a/apps/docs/pages/guides/functions.mdx +++ b/apps/docs/pages/guides/functions.mdx @@ -29,7 +29,7 @@ Check out the [Edge Function Examples](https://github.com/supabase/supabase/tree
    {examples.map((x) => ( -
    +
    From 5322a76451157591b61153930b5b44cdc7b57596 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Tue, 22 Nov 2022 16:37:49 +0800 Subject: [PATCH 165/215] feat: refactor preset reports to use hook for data fetching --- .../interfaces/Reports/PresetReport.tsx | 113 ------------------ .../interfaces/Reports/Reports.constants.ts | 10 +- .../interfaces/Reports/Reports.types.ts | 5 + .../interfaces/Reports/Reports.utils.ts | 7 -- .../interfaces/Reports/Reports.utils.tsx | 86 +++++++++++++ .../ReportsLayout/ReportsMenu.utils.ts | 9 +- studio/hooks/analytics/useLogsQuery.tsx | 6 +- .../project/[ref]/reports/api-overview.tsx | 65 ++++++++++ .../pages/project/[ref]/reports/api-usage.tsx | 18 --- 9 files changed, 172 insertions(+), 147 deletions(-) delete mode 100644 studio/components/interfaces/Reports/PresetReport.tsx delete mode 100644 studio/components/interfaces/Reports/Reports.utils.ts create mode 100644 studio/components/interfaces/Reports/Reports.utils.tsx create mode 100644 studio/pages/project/[ref]/reports/api-overview.tsx delete mode 100644 studio/pages/project/[ref]/reports/api-usage.tsx diff --git a/studio/components/interfaces/Reports/PresetReport.tsx b/studio/components/interfaces/Reports/PresetReport.tsx deleted file mode 100644 index bcf7ae5ad0d..00000000000 --- a/studio/components/interfaces/Reports/PresetReport.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { Button, IconRefreshCw } from 'ui' -import useLogsQuery from 'hooks/analytics/useLogsQuery' -import { DatePickerToFrom } from '../Settings/Logs' -import DatePickers from '../Settings/Logs/Logs.DatePickers' -import { - renderErrorRateChart, - renderRequestsPathsTable, - renderStatusCodesChart, -} from './renderers/OverviewRenderers' -import { PRESET_CONFIG, REPORTS_DATEPICKER_HELPERS } from './Reports.constants' -import { Presets } from './Reports.types' -import ReportWidget from './ReportWidget' - -interface Props { - preset: Presets - projectRef: string -} - -const DEFAULT_PARAMS = { - iso_timestamp_start: REPORTS_DATEPICKER_HELPERS[0].calcFrom(), - iso_timestamp_end: REPORTS_DATEPICKER_HELPERS[0].calcTo(), -} - -const PresetReport: React.FC = ({ projectRef, preset }) => { - const config = PRESET_CONFIG[preset] - const [statusCodes, statusCodesHandlers] = useLogsQuery(projectRef, { - sql: config.sql.statusCodes, - ...DEFAULT_PARAMS, - }) - - const [requestPaths, requestPathsHandlers] = useLogsQuery(projectRef, { - sql: config.sql.requestPaths, - ...DEFAULT_PARAMS, - }) - - const [errorRate, errorRateHandlers] = useLogsQuery(projectRef, { - sql: config.sql.errorRate, - ...DEFAULT_PARAMS, - }) - - const handleRefresh = () => { - statusCodesHandlers.runQuery() - requestPathsHandlers.runQuery() - errorRateHandlers.runQuery() - } - const handleDatepickerChange = ({ to, from }: DatePickerToFrom) => { - const newParams = { iso_timestamp_start: from || '', iso_timestamp_end: to || '' } - statusCodesHandlers.setParams((prev) => ({ ...prev, ...newParams })) - requestPathsHandlers.setParams((prev) => ({ ...prev, ...newParams })) - errorRateHandlers.setParams((prev) => ({ ...prev, ...newParams })) - } - const isLoading = statusCodes.isLoading || requestPaths.isLoading - - return ( -
    -

    {config.title}

    -
    - - -
    - -
    - - - -
    -
    - ) -} - -export default PresetReport diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index ac4492a7075..e334c0485b8 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -1,6 +1,6 @@ import dayjs from 'dayjs' import { DatetimeHelper } from '../Settings/Logs' -import { Presets } from './Reports.types' +import { PresetConfig, Presets } from './Reports.types' export const LAYOUT_COLUMN_COUNT = 24 @@ -28,7 +28,13 @@ export const REPORTS_DATEPICKER_HELPERS: DatetimeHelper[] = [ }, ] -export const PRESET_CONFIG = { +export const DEFAULT_QUERY_PARAMS = { + iso_timestamp_start: REPORTS_DATEPICKER_HELPERS[0].calcFrom(), + iso_timestamp_end: REPORTS_DATEPICKER_HELPERS[0].calcTo(), +} + + +export const PRESET_CONFIG : Record ={ [Presets.OVERVIEW]: { title: '', sql: { diff --git a/studio/components/interfaces/Reports/Reports.types.ts b/studio/components/interfaces/Reports/Reports.types.ts index 1838192894f..3679feeb5ca 100644 --- a/studio/components/interfaces/Reports/Reports.types.ts +++ b/studio/components/interfaces/Reports/Reports.types.ts @@ -1,6 +1,11 @@ export enum Presets { OVERVIEW = 'overview', } + +export interface PresetConfig { + title: string + sql: Record +} export interface StatusCodesDatum { timestamp: number status_code: number diff --git a/studio/components/interfaces/Reports/Reports.utils.ts b/studio/components/interfaces/Reports/Reports.utils.ts deleted file mode 100644 index 12a7d634797..00000000000 --- a/studio/components/interfaces/Reports/Reports.utils.ts +++ /dev/null @@ -1,7 +0,0 @@ - -/** - * Converts a query params string to an object - */ -export const queryParamsToObject = (params: string) => { - return Object.fromEntries(new URLSearchParams(params)) -} \ No newline at end of file diff --git a/studio/components/interfaces/Reports/Reports.utils.tsx b/studio/components/interfaces/Reports/Reports.utils.tsx new file mode 100644 index 00000000000..659362a0a9c --- /dev/null +++ b/studio/components/interfaces/Reports/Reports.utils.tsx @@ -0,0 +1,86 @@ +import { IconRefreshCw } from '@supabase/ui' +import useLogsQuery, { LogsQueryData, LogsQueryHandlers } from 'hooks/analytics/useLogsQuery' +import React from 'react' +import { Button } from 'ui' +import { DatePickerToFrom } from '../Settings/Logs' +import DatePickers from '../Settings/Logs/Logs.DatePickers' +import { DEFAULT_QUERY_PARAMS, REPORTS_DATEPICKER_HELPERS } from './Reports.constants' +import { PresetConfig } from './Reports.types' + +/** + * Converts a query params string to an object + */ +export const queryParamsToObject = (params: string) => { + return Object.fromEntries(new URLSearchParams(params)) +} + +// generate hooks based on preset config +type PresetHooks = Record [LogsQueryData, LogsQueryHandlers]> +export const hooksFactory = (projectRef: string, config: PresetConfig) => { + const hooks: PresetHooks = Object.entries(config.sql).reduce((acc, [k, sql]) => { + return { + ...acc, + [k]: () => + useLogsQuery(projectRef, { + sql, + ...DEFAULT_QUERY_PARAMS, + }), + } + }, {}) + console.log(hooks) + return hooks +} + +export const usePresetReport = (data: LogsQueryData[], handlers: LogsQueryHandlers[]) => { + const handleRefresh = () => { + handlers.forEach((handler) => { + handler.runQuery() + }) + } + const handleDatepickerChange = ({ to, from }: DatePickerToFrom) => { + const newParams = { iso_timestamp_start: from || '', iso_timestamp_end: to || '' } + + handlers.forEach((handler) => { + handler.setParams((prev) => ({ + ...prev, + ...newParams, + })) + }) + } + const isLoading = data.map((datum) => datum.isLoading).some((v) => v) + const Layout: React.FC<{title: string}> = ({ title, children }) => ( +
    +

    {title}

    +
    + + +
    + + {children} +
    + ) + return { + isLoading, + Layout, + handleRefresh, + handleDatepickerChange, + } +} diff --git a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts index 28c14e91e92..49c75e4afa9 100644 --- a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts +++ b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts @@ -19,10 +19,11 @@ export const generateReportsMenu = (project?: Project): ProductMenuGroup[] => { ...(productReports ? [ { - name: 'API usage', - key: 'api-usage', - url: `/project/${ref}/reports/api-usage`, - items: [], + name: 'API', + key: 'api-overview', + url: `/project/${ref}/reports/api-overview`, + items: [] + }, }, ] : []), diff --git a/studio/hooks/analytics/useLogsQuery.tsx b/studio/hooks/analytics/useLogsQuery.tsx index 7b102dbc2d5..48721a081ae 100644 --- a/studio/hooks/analytics/useLogsQuery.tsx +++ b/studio/hooks/analytics/useLogsQuery.tsx @@ -8,13 +8,13 @@ import { LogsEndpointParams, Logs, LogData } from 'components/interfaces/Setting import { API_URL } from 'lib/constants' import useSWR from 'swr' import { get } from 'lib/common/fetch' -interface Data { +export interface LogsQueryData { params: LogsEndpointParams isLoading: boolean logData: LogData[] error: string | Object | null } -interface Handlers { +export interface LogsQueryHandlers { changeQuery: (newQuery?: string) => void runQuery: () => void setParams: Dispatch> @@ -23,7 +23,7 @@ interface Handlers { const useLogsQuery = ( projectRef: string, initialParams: Partial = {} -): [Data, Handlers] => { +): [LogsQueryData, LogsQueryHandlers] => { const defaultHelper = getDefaultHelper(EXPLORER_DATEPICKER_HELPERS) const [params, setParams] = useState({ sql: initialParams?.sql || '', diff --git a/studio/pages/project/[ref]/reports/api-overview.tsx b/studio/pages/project/[ref]/reports/api-overview.tsx new file mode 100644 index 00000000000..40ed529d425 --- /dev/null +++ b/studio/pages/project/[ref]/reports/api-overview.tsx @@ -0,0 +1,65 @@ +import { observer } from 'mobx-react-lite' +import { useRouter } from 'next/router' + +import { NextPageWithLayout } from 'types' +import { Presets } from 'components/interfaces/Reports/Reports.types' +import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout' +import { PRESET_CONFIG } from 'components/interfaces/Reports/Reports.constants' +import ReportWidget from 'components/interfaces/Reports/ReportWidget' +import { hooksFactory, usePresetReport } from 'components/interfaces/Reports/Reports.utils' +import { + renderErrorRateChart, + renderRequestsPathsTable, + renderStatusCodesChart, +} from 'components/interfaces/Reports/renderers/OverviewRenderers' + +export const ApiOverviewPage: NextPageWithLayout = () => { + const router = useRouter() + const { ref } = router.query + const config = PRESET_CONFIG[Presets.OVERVIEW] + const hooks = hooksFactory(ref as string, config) + const [statusCodes, statusCodesHandlers] = hooks.statusCodes() + const [errorRate, errorRateHandlers] = hooks.errorRate() + const [requestPaths, requestPathsHandlers] = hooks.requestPaths() + const { isLoading, Layout } = usePresetReport( + [statusCodes, errorRate, requestPaths], + [statusCodesHandlers, errorRateHandlers, requestPathsHandlers] + ) + return ( + +
    + + + +
    +
    + ) +} + +ApiOverviewPage.getLayout = (page) => {page} + +export default observer(ApiOverviewPage) diff --git a/studio/pages/project/[ref]/reports/api-usage.tsx b/studio/pages/project/[ref]/reports/api-usage.tsx deleted file mode 100644 index 9a027edf251..00000000000 --- a/studio/pages/project/[ref]/reports/api-usage.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { observer } from 'mobx-react-lite' -import { useRouter } from 'next/router' - -import { NextPageWithLayout } from 'types' -import PresetReport from 'components/interfaces/Reports/PresetReport' -import { Presets } from 'components/interfaces/Reports/Reports.types' -import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout' - -export const ApiUsagePage: NextPageWithLayout = () => { - const router = useRouter() - const { ref } = router.query - - return -} - -ApiUsagePage.getLayout = (page) => {page} - -export default observer(ApiUsagePage) From de385d38c91972649ce453e2cd47759764b4c026 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Tue, 22 Nov 2022 17:38:55 +0800 Subject: [PATCH 166/215] feat: add api bots report --- .../interfaces/Reports/Reports.constants.ts | 54 ++++++++++- .../interfaces/Reports/Reports.types.ts | 3 +- .../Reports/renderers/ApiBotsRenderers.tsx | 90 +++++++++++++++++++ .../ReportsLayout/ReportsMenu.utils.ts | 15 +++- .../ui/ProductMenu/ProductMenuGroup.tsx | 3 + .../pages/project/[ref]/reports/api-bots.tsx | 51 +++++++++++ .../project/[ref]/reports/api-overview.tsx | 2 +- 7 files changed, 212 insertions(+), 6 deletions(-) create mode 100644 studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx create mode 100644 studio/components/ui/ProductMenu/ProductMenuGroup.tsx create mode 100644 studio/pages/project/[ref]/reports/api-bots.tsx diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index e334c0485b8..ea6005b890c 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -35,7 +35,8 @@ export const DEFAULT_QUERY_PARAMS = { export const PRESET_CONFIG : Record ={ - [Presets.OVERVIEW]: { + + [Presets.API_OVERVIEW]: { title: '', sql: { statusCodes: ` @@ -95,6 +96,57 @@ ORDER BY `, }, }, + [Presets.API_BOTS]: { + title: "API Bots", + sql: { + userAgents: ` +select + h.user_agent as user_agent, + cf.asOrganization as request_source, + count(f.id) as count +from + edge_logs as f + cross join unnest(f.metadata) as m + cross join unnest(m.request) as r + cross join unnest(r.cf) as cf + cross join unnest(r.headers) as h +group by + user_agent, + request_source +order by + count desc +limit 12 +`, + botScores:` +select + h.cf_connecting_ip as ip, + h.cf_ipcountry as country, + h.user_agent as user_agent, + r.path as path, + b.score as bot_score, + b.verifiedBot as bot_verified, + count(f.id) as count +from + edge_logs as f + cross join unnest(f.metadata) as m + cross join unnest(m.request) as r + cross join unnest(r.cf) as cf + cross join unnest(r.headers) as h + cross join unnest(cf.botManagement) as b +group by + ip, + country, + path, + user_agent, + bot_score, + bot_verified +order by + bot_score desc +limit 20 + + ` + } + } } export const DATETIME_FORMAT = 'MMM D, ha' diff --git a/studio/components/interfaces/Reports/Reports.types.ts b/studio/components/interfaces/Reports/Reports.types.ts index 3679feeb5ca..359b9bd7569 100644 --- a/studio/components/interfaces/Reports/Reports.types.ts +++ b/studio/components/interfaces/Reports/Reports.types.ts @@ -1,5 +1,6 @@ export enum Presets { - OVERVIEW = 'overview', + API_OVERVIEW = 'overview', + API_BOTS="api_bots" } export interface PresetConfig { diff --git a/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx b/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx new file mode 100644 index 00000000000..22f47142c32 --- /dev/null +++ b/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx @@ -0,0 +1,90 @@ +import Table from 'components/to-be-cleaned/Table' +import { ReportWidgetProps } from '../ReportWidget' + +export const renderUserAgents = ( + props: ReportWidgetProps<{ + user_agent: string + request_source: string + count: number + }> +) => { + return ( + + User Agents + Request Source + Count + + } + body={ + <> + {props.data.map((row, index) => { + return ( + + + {row.user_agent} + + {row.request_source} + + {row.count} + + + ) + })} + + } + /> + ) +} + +export const renderBotScores = ( + props: ReportWidgetProps<{ + ip: string + country: string + user_agent: string + path: string + bot_score: number + bot_verified: boolean + count: number + }> +) => { + return ( +
    + IP (Country) + Request Path + User Agent + Bot Score + Is Verified Bot + Count + + } + body={ + <> + {props.data.map((row, index) => { + return ( + + + {row.ip} ({row.country}) + + {row.path} + {row.user_agent} + {row.bot_score} + {row.bot_verified ? 'Yes' : 'No'} + + {row.count} + + + ) + })} + + } + /> + ) +} \ No newline at end of file diff --git a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts index 49c75e4afa9..427476af448 100644 --- a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts +++ b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts @@ -8,7 +8,7 @@ export const generateReportsMenu = (project?: Project): ProductMenuGroup[] => { return [ { - title: 'Reports', + title: '', items: [ { name: 'Custom reports', @@ -16,14 +16,23 @@ export const generateReportsMenu = (project?: Project): ProductMenuGroup[] => { url: `/project/${ref}/reports`, items: [], }, + ], + }, + { + items: [ ...(productReports ? [ { - name: 'API', + name: 'API Overview', key: 'api-overview', url: `/project/${ref}/reports/api-overview`, - items: [] + items: [], }, + { + name: 'API Bots', + key: 'api-bots', + url: `/project/${ref}/reports/api-bots`, + items: [], }, ] : []), diff --git a/studio/components/ui/ProductMenu/ProductMenuGroup.tsx b/studio/components/ui/ProductMenu/ProductMenuGroup.tsx new file mode 100644 index 00000000000..96f4ea20ef0 --- /dev/null +++ b/studio/components/ui/ProductMenu/ProductMenuGroup.tsx @@ -0,0 +1,3 @@ +const ProductMenuGroup = ()=>{ + +} \ No newline at end of file diff --git a/studio/pages/project/[ref]/reports/api-bots.tsx b/studio/pages/project/[ref]/reports/api-bots.tsx new file mode 100644 index 00000000000..40f518ae010 --- /dev/null +++ b/studio/pages/project/[ref]/reports/api-bots.tsx @@ -0,0 +1,51 @@ +import { observer } from 'mobx-react-lite' +import { useRouter } from 'next/router' + +import { NextPageWithLayout } from 'types' +import { Presets } from 'components/interfaces/Reports/Reports.types' +import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout' +import { hooksFactory, usePresetReport } from 'components/interfaces/Reports/Reports.utils' +import { PRESET_CONFIG } from 'components/interfaces/Reports/Reports.constants' +import { renderBotScores, renderUserAgents } from 'components/interfaces/Reports/renderers/ApiBotsRenderers' +import ReportWidget from 'components/interfaces/Reports/ReportWidget' + +export const ApiBotsPage: NextPageWithLayout = () => { + const router = useRouter() + const { ref } = router.query + const config = PRESET_CONFIG[Presets.API_BOTS] + const hooks = hooksFactory(ref as string, config) + const [userAgents, userAgentsHandlers] = hooks.userAgents() + const [botScores, botScoresHandlers] = hooks.botScores() + const { isLoading, Layout } = usePresetReport( + [userAgents, botScores], + [userAgentsHandlers, botScoresHandlers] + ) + return ( + +
    + + +
    +
    + ) +} + +ApiBotsPage.getLayout = (page) => {page} + +export default observer(ApiBotsPage) diff --git a/studio/pages/project/[ref]/reports/api-overview.tsx b/studio/pages/project/[ref]/reports/api-overview.tsx index 40ed529d425..cd211996a34 100644 --- a/studio/pages/project/[ref]/reports/api-overview.tsx +++ b/studio/pages/project/[ref]/reports/api-overview.tsx @@ -16,7 +16,7 @@ import { export const ApiOverviewPage: NextPageWithLayout = () => { const router = useRouter() const { ref } = router.query - const config = PRESET_CONFIG[Presets.OVERVIEW] + const config = PRESET_CONFIG[Presets.API_OVERVIEW] const hooks = hooksFactory(ref as string, config) const [statusCodes, statusCodesHandlers] = hooks.statusCodes() const [errorRate, errorRateHandlers] = hooks.errorRate() From de04637d4968895142f9cf4af085b32cb4e6b482 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Tue, 22 Nov 2022 20:04:48 +0800 Subject: [PATCH 167/215] feat: add auth report --- .../interfaces/Reports/Reports.constants.ts | 47 ++++++++++++---- .../interfaces/Reports/Reports.types.ts | 3 +- .../Reports/renderers/AuthRenderers.tsx | 47 ++++++++++++++++ .../ReportsLayout/ReportsMenu.utils.ts | 6 +++ studio/pages/project/[ref]/reports/auth.tsx | 54 +++++++++++++++++++ 5 files changed, 147 insertions(+), 10 deletions(-) create mode 100644 studio/components/interfaces/Reports/renderers/AuthRenderers.tsx create mode 100644 studio/pages/project/[ref]/reports/auth.tsx diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index ea6005b890c..c2a81041b29 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -33,9 +33,7 @@ export const DEFAULT_QUERY_PARAMS = { iso_timestamp_end: REPORTS_DATEPICKER_HELPERS[0].calcTo(), } - -export const PRESET_CONFIG : Record ={ - +export const PRESET_CONFIG: Record = { [Presets.API_OVERVIEW]: { title: '', sql: { @@ -97,7 +95,7 @@ ORDER BY }, }, [Presets.API_BOTS]: { - title: "API Bots", + title: 'API Bots', sql: { userAgents: ` select @@ -117,7 +115,7 @@ order by count desc limit 12 `, - botScores:` + botScores: ` select h.cf_connecting_ip as ip, h.cf_ipcountry as country, @@ -143,10 +141,41 @@ group by order by bot_score desc limit 20 - - ` - } - } + `, + }, + }, + [Presets.AUTH]: { + title: 'Auth', + sql: { + dailyActiveUsers: ` +select + timestamp_trunc(timestamp, day) as timestamp, + count(distinct json_extract(f.event_message, "$.user_id")) as count +from gotrue_logs f +cross join unnest(f.metadata) as m +where json_extract(f.event_message, "$.user_id") is not null +group by + timestamp +order by + timestamp +`, +newUsers: ` +select + TIMESTAMP_TRUNC(f.timestamp, DAY) as timestamp, + count(id) as count +from +gotrue_logs f + cross join unnest(f.metadata) as m +where f.timestamp > "2022-11-20" +and m.project = "tzljvicuuxegpzwuwife" +and JSON_VALUE(f.event_message, "$.path") = "/signup" +and JSON_VALUE(f.event_message, "$.auth_event.actor_id") is not null +and JSON_VALUE(f.event_message, "$.auth_event.action") != "user_repeated_signup" +group by + timestamp +` + }, + }, } export const DATETIME_FORMAT = 'MMM D, ha' diff --git a/studio/components/interfaces/Reports/Reports.types.ts b/studio/components/interfaces/Reports/Reports.types.ts index 359b9bd7569..5ace186ea57 100644 --- a/studio/components/interfaces/Reports/Reports.types.ts +++ b/studio/components/interfaces/Reports/Reports.types.ts @@ -1,6 +1,7 @@ export enum Presets { API_OVERVIEW = 'overview', - API_BOTS="api_bots" + API_BOTS = 'api_bots', + AUTH = 'auth', } export interface PresetConfig { diff --git a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx new file mode 100644 index 00000000000..c6a05709fe9 --- /dev/null +++ b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx @@ -0,0 +1,47 @@ +import { BarChart } from 'components/to-be-cleaned/Charts/ChartRenderer' +import { DATETIME_FORMAT } from '../Reports.constants' +import { ReportWidgetProps } from '../ReportWidget' + +export const renderDailyActiveUsers = ( + props: ReportWidgetProps<{ + count: number + timestamp: string + }> +) => { + return ( + <> +
    + +
    + + ) +} + +export const renderNewUsers = ( + props: ReportWidgetProps<{ + count: number + timestamp: string + }> +) => { + return ( + <> +
    + +
    + + ) +} diff --git a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts index 427476af448..c120e0fbb64 100644 --- a/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts +++ b/studio/components/layouts/ReportsLayout/ReportsMenu.utils.ts @@ -34,6 +34,12 @@ export const generateReportsMenu = (project?: Project): ProductMenuGroup[] => { url: `/project/${ref}/reports/api-bots`, items: [], }, + { + name: 'Auth', + key: 'auth', + url: `/project/${ref}/reports/auth`, + items: [], + }, ] : []), { diff --git a/studio/pages/project/[ref]/reports/auth.tsx b/studio/pages/project/[ref]/reports/auth.tsx new file mode 100644 index 00000000000..2a33a8d6837 --- /dev/null +++ b/studio/pages/project/[ref]/reports/auth.tsx @@ -0,0 +1,54 @@ +import { observer } from 'mobx-react-lite' +import { useRouter } from 'next/router' + +import { NextPageWithLayout } from 'types' +import { Presets } from 'components/interfaces/Reports/Reports.types' +import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout' +import { hooksFactory, usePresetReport } from 'components/interfaces/Reports/Reports.utils' +import { PRESET_CONFIG } from 'components/interfaces/Reports/Reports.constants' +import ReportWidget from 'components/interfaces/Reports/ReportWidget' +import { + renderDailyActiveUsers, + renderNewUsers, +} from 'components/interfaces/Reports/renderers/AuthRenderers' + +export const ApiBotsPage: NextPageWithLayout = () => { + const router = useRouter() + const { ref } = router.query + const config = PRESET_CONFIG[Presets.AUTH] + const hooks = hooksFactory(ref as string, config) + const [dailyActiveUsers, dailyActiveUsersHandlers] = hooks.dailyActiveUsers() + const [newUsers, newUsersHandlers] = hooks.newUsers() + const { isLoading, Layout } = usePresetReport( + [dailyActiveUsers, newUsers], + [dailyActiveUsersHandlers, newUsersHandlers] + ) + return ( + +
    + + +
    +
    + ) +} + +ApiBotsPage.getLayout = (page) => {page} + +export default observer(ApiBotsPage) From 31880c2740a4f77a78aa6ad84d769183ef2ed9b5 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Mon, 5 Dec 2022 17:52:55 +0800 Subject: [PATCH 168/215] feat: refactored hook generation logic --- .../interfaces/Reports/Reports.constants.ts | 233 ++++++++++-------- .../interfaces/Reports/Reports.types.ts | 17 +- .../interfaces/Reports/Reports.utils.tsx | 55 +++-- studio/hooks/analytics/useDbQuery.tsx | 37 +++ .../pages/project/[ref]/reports/api-bots.tsx | 13 +- .../project/[ref]/reports/api-overview.tsx | 23 +- studio/pages/project/[ref]/reports/auth.tsx | 13 +- 7 files changed, 237 insertions(+), 154 deletions(-) create mode 100644 studio/hooks/analytics/useDbQuery.tsx diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index c2a81041b29..2f99d265a3d 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -36,118 +36,135 @@ export const DEFAULT_QUERY_PARAMS = { export const PRESET_CONFIG: Record = { [Presets.API_OVERVIEW]: { title: '', - sql: { - statusCodes: ` - select - timestamp_trunc(timestamp, hour) as timestamp, - r.status_code as status_code, - count(status_code) as count - FROM - edge_logs - CROSS JOIN UNNEST(metadata) AS m - CROSS JOIN UNNEST(m.response) AS r - GROUP BY - timestamp, - status_code - ORDER BY - timestamp ASC`, - errorRate: ` -select - timestamp_trunc(timestamp, hour) as timestamp, - count(timestamp) as count -FROM - edge_logs - LEFT JOIN UNNEST(metadata) AS f1 ON TRUE - LEFT JOIN UNNEST(f1.response) AS f3 ON TRUE -WHERE - f3.status_code >= 400 -GROUP BY - timestamp -ORDER BY - timestamp ASC -`, - requestPaths: ` - select - f2.path as path, - f2.search as query_params, - f2.method as method, - f3.status_code as status_code, - avg(f3.origin_time) as avg_origin_time, - sum(f3.origin_time) as sum, - APPROX_QUANTILES(f3.origin_time, 100) as quantiles, - count(timestamp) as count - FROM - edge_logs - LEFT JOIN UNNEST(metadata) AS f1 ON TRUE - LEFT JOIN UNNEST(f1.request) AS f2 ON TRUE - LEFT JOIN UNNEST(f1.response) AS f3 ON TRUE - GROUP BY - path, - query_params, - method, - status_code - ORDER BY - sum DESC, - count desc, - avg_origin_time DESC - LIMIT 50 -`, + queries: { + statusCodes: { + queryType: 'logs', + sql: ` + select + timestamp_trunc(timestamp, hour) as timestamp, + r.status_code as status_code, + count(status_code) as count + FROM + edge_logs + CROSS JOIN UNNEST(metadata) AS m + CROSS JOIN UNNEST(m.response) AS r + GROUP BY + timestamp, + status_code + ORDER BY + timestamp ASC`, + }, + errorRates: { + queryType: 'logs', + sql: ` + select + timestamp_trunc(timestamp, hour) as timestamp, + count(timestamp) as count + FROM + edge_logs + LEFT JOIN UNNEST(metadata) AS f1 ON TRUE + LEFT JOIN UNNEST(f1.response) AS f3 ON TRUE + WHERE + f3.status_code >= 400 + GROUP BY + timestamp + ORDER BY + timestamp ASC + `, + }, + requestPaths: { + queryType: 'logs', + sql: ` + select + f2.path as path, + f2.search as query_params, + f2.method as method, + f3.status_code as status_code, + avg(f3.origin_time) as avg_origin_time, + sum(f3.origin_time) as sum, + APPROX_QUANTILES(f3.origin_time, 100) as quantiles, + count(timestamp) as count + FROM + edge_logs + LEFT JOIN UNNEST(metadata) AS f1 ON TRUE + LEFT JOIN UNNEST(f1.request) AS f2 ON TRUE + LEFT JOIN UNNEST(f1.response) AS f3 ON TRUE + GROUP BY + path, + query_params, + method, + status_code + ORDER BY + sum DESC, + count desc, + avg_origin_time DESC + LIMIT 50 + `, + }, }, }, [Presets.API_BOTS]: { title: 'API Bots', - sql: { - userAgents: ` -select - h.user_agent as user_agent, - cf.asOrganization as request_source, - count(f.id) as count -from - edge_logs as f - cross join unnest(f.metadata) as m - cross join unnest(m.request) as r - cross join unnest(r.cf) as cf - cross join unnest(r.headers) as h -group by - user_agent, - request_source -order by - count desc -limit 12 -`, - botScores: ` -select - h.cf_connecting_ip as ip, - h.cf_ipcountry as country, - h.user_agent as user_agent, - r.path as path, - b.score as bot_score, - b.verifiedBot as bot_verified, - count(f.id) as count -from - edge_logs as f - cross join unnest(f.metadata) as m - cross join unnest(m.request) as r - cross join unnest(r.cf) as cf - cross join unnest(r.headers) as h - cross join unnest(cf.botManagement) as b -group by - ip, - country, - path, - user_agent, - bot_score, - bot_verified -order by - bot_score desc -limit 20 - `, + queries: { + userAgents: { + queryType: 'logs', + sql: ` + select + h.user_agent as user_agent, + cf.asOrganization as request_source, + count(f.id) as count + from + edge_logs as f + cross join unnest(f.metadata) as m + cross join unnest(m.request) as r + cross join unnest(r.cf) as cf + cross join unnest(r.headers) as h + group by + user_agent, + request_source + order by + count desc + limit 12 + `, + }, + botScores: { + queryType: 'logs', + sql: ` + select + h.cf_connecting_ip as ip, + h.cf_ipcountry as country, + h.user_agent as user_agent, + r.path as path, + b.score as bot_score, + b.verifiedBot as bot_verified, + count(f.id) as count + from + edge_logs as f + cross join unnest(f.metadata) as m + cross join unnest(m.request) as r + cross join unnest(r.cf) as cf + cross join unnest(r.headers) as h + cross join unnest(cf.botManagement) as b + group by + ip, + country, + path, + user_agent, + bot_score, + bot_verified + order by + bot_score desc + limit 20 + `, + }, }, }, [Presets.AUTH]: { title: 'Auth', - sql: { - dailyActiveUsers: ` + queries: { + dailyActiveUsers: { + queryType: 'logs', + sql: ` select timestamp_trunc(timestamp, day) as timestamp, count(distinct json_extract(f.event_message, "$.user_id")) as count @@ -159,7 +176,10 @@ group by order by timestamp `, -newUsers: ` + }, + newUsers: { + queryType: 'logs', + sql: ` select TIMESTAMP_TRUNC(f.timestamp, DAY) as timestamp, count(id) as count @@ -173,7 +193,8 @@ and JSON_VALUE(f.event_message, "$.auth_event.actor_id") is not null and JSON_VALUE(f.event_message, "$.auth_event.action") != "user_repeated_signup" group by timestamp -` +`, + }, }, }, } diff --git a/studio/components/interfaces/Reports/Reports.types.ts b/studio/components/interfaces/Reports/Reports.types.ts index 5ace186ea57..f86e32ef143 100644 --- a/studio/components/interfaces/Reports/Reports.types.ts +++ b/studio/components/interfaces/Reports/Reports.types.ts @@ -4,9 +4,24 @@ export enum Presets { AUTH = 'auth', } +export interface BaseReportParams { + iso_timestamp_start: string + iso_timestamp_end: string +} + +export type VariableSql = string | ((params: BaseReportParams) => string) + +export type PresetSql = string | VariableSql + export interface PresetConfig { title: string - sql: Record + queries: Record< + string, + { + sql: PresetSql + queryType: 'logs' | 'db' + } + > } export interface StatusCodesDatum { timestamp: number diff --git a/studio/components/interfaces/Reports/Reports.utils.tsx b/studio/components/interfaces/Reports/Reports.utils.tsx index 659362a0a9c..1f624714b2c 100644 --- a/studio/components/interfaces/Reports/Reports.utils.tsx +++ b/studio/components/interfaces/Reports/Reports.utils.tsx @@ -1,4 +1,5 @@ import { IconRefreshCw } from '@supabase/ui' +import useDbQuery, { DbQueryData, DbQueryHandler } from 'hooks/analytics/useDbQuery' import useLogsQuery, { LogsQueryData, LogsQueryHandlers } from 'hooks/analytics/useLogsQuery' import React from 'react' import { Button } from 'ui' @@ -15,23 +16,35 @@ export const queryParamsToObject = (params: string) => { } // generate hooks based on preset config -type PresetHooks = Record [LogsQueryData, LogsQueryHandlers]> +type PresetHookResult = [LogsQueryData, LogsQueryHandlers] +type PresetHooks = Record PresetHookResult> export const hooksFactory = (projectRef: string, config: PresetConfig) => { - const hooks: PresetHooks = Object.entries(config.sql).reduce((acc, [k, sql]) => { - return { - ...acc, - [k]: () => - useLogsQuery(projectRef, { - sql, - ...DEFAULT_QUERY_PARAMS, - }), - } - }, {}) - console.log(hooks) + const hooks: PresetHooks = Object.entries(config.queries).reduce( + (acc, [k, { sql, queryType }]) => { + if (queryType === 'db') { + return { + ...acc, + [k]: () => useDbQuery(sql), + } + } else { + return { + ...acc, + [k]: () => + useLogsQuery(projectRef, { + sql: sql as string, + ...DEFAULT_QUERY_PARAMS, + }), + } + } + }, + {} + ) return hooks } -export const usePresetReport = (data: LogsQueryData[], handlers: LogsQueryHandlers[]) => { +export const usePresetReport = (hooks: PresetHookResult[]) => { + const data = hooks.map((h) => h[0]) + const handlers = hooks.map((h) => h[1]) const handleRefresh = () => { handlers.forEach((handler) => { handler.runQuery() @@ -41,21 +54,23 @@ export const usePresetReport = (data: LogsQueryData[], handlers: LogsQueryHandle const newParams = { iso_timestamp_start: from || '', iso_timestamp_end: to || '' } handlers.forEach((handler) => { - handler.setParams((prev) => ({ - ...prev, - ...newParams, - })) + if (handler.setParams) { + handler.setParams((prev) => ({ + ...prev, + ...newParams, + })) + } }) } const isLoading = data.map((datum) => datum.isLoading).some((v) => v) - const Layout: React.FC<{title: string}> = ({ title, children }) => ( + const Layout: React.FC<{ title: string }> = ({ title, children }) => (

    {title}

    @@ -45,6 +45,6 @@ export const ApiBotsPage: NextPageWithLayout = () => { ) } -ApiBotsPage.getLayout = (page) => {page} +ApiBotsReport.getLayout = (page) => {page} -export default observer(ApiBotsPage) +export default observer(ApiBotsReport) diff --git a/studio/pages/project/[ref]/reports/api-overview.tsx b/studio/pages/project/[ref]/reports/api-overview.tsx index 7f2881877bb..ad9c4d2ecdc 100644 --- a/studio/pages/project/[ref]/reports/api-overview.tsx +++ b/studio/pages/project/[ref]/reports/api-overview.tsx @@ -13,7 +13,7 @@ import { renderStatusCodesChart, } from 'components/interfaces/Reports/renderers/OverviewRenderers' -export const ApiOverviewPage: NextPageWithLayout = () => { +export const ApiOverviewReport: NextPageWithLayout = () => { const router = useRouter() const { ref } = router.query const config = PRESET_CONFIG[Presets.API_OVERVIEW] @@ -31,7 +31,7 @@ export const ApiOverviewPage: NextPageWithLayout = () => { className="col-span-4 col-start-1" title="API Status Codes" description="Distribution of API responses by status codes." - data={statusCodes[0].logData} + data={statusCodes[0].logData || []} renderer={renderStatusCodesChart} /> { className="col-span-4 col-start-1" title="Error Rate" description="Percentage of API 5XX and 4XX error responses." - data={errorRates[0].logData} + data={errorRates[0].logData || []} renderer={renderErrorRateChart} /> { className="col-span-4 col-start-1" title="Slow API Requests" description="Most frequently used requests, sorted by total querying time" - data={requestPaths[0].logData} + data={requestPaths[0].logData || []} renderer={renderRequestsPathsTable} />
    @@ -57,6 +57,6 @@ export const ApiOverviewPage: NextPageWithLayout = () => { ) } -ApiOverviewPage.getLayout = (page) => {page} +ApiOverviewReport.getLayout = (page) => {page} -export default observer(ApiOverviewPage) +export default observer(ApiOverviewReport) diff --git a/studio/pages/project/[ref]/reports/auth.tsx b/studio/pages/project/[ref]/reports/auth.tsx index 1d1557f2444..5e6c6781a79 100644 --- a/studio/pages/project/[ref]/reports/auth.tsx +++ b/studio/pages/project/[ref]/reports/auth.tsx @@ -13,7 +13,7 @@ import { renderCumulativeUsers, } from 'components/interfaces/Reports/renderers/AuthRenderers' -export const ApiBotsPage: NextPageWithLayout = () => { +export const AuthReport: NextPageWithLayout = () => { const router = useRouter() const { ref } = router.query const config = PRESET_CONFIG[Presets.AUTH] @@ -31,33 +31,33 @@ export const ApiBotsPage: NextPageWithLayout = () => { params={cumulativeUsers[0].params} className="col-span-2 col-start-1" title="Users Over Time" - description="Daily count of confirmed users" + description="Cumulative count of confirmed users" data={cumulativeUsers[0].data!} renderer={renderCumulativeUsers} /> + - ) } -ApiBotsPage.getLayout = (page) => {page} +AuthReport.getLayout = (page) => {page} -export default observer(ApiBotsPage) +export default observer(AuthReport) diff --git a/studio/tests/components/Reports/PresetReport.test.js b/studio/tests/components/Reports/PresetReport.test.js deleted file mode 100644 index 7813c376821..00000000000 --- a/studio/tests/components/Reports/PresetReport.test.js +++ /dev/null @@ -1,84 +0,0 @@ -import dayjs from 'dayjs' -import utc from 'dayjs/plugin/utc' - -dayjs.extend(utc) - -// mock the fetch function -jest.mock('lib/common/fetch') -import { get } from 'lib/common/fetch' - -// mock mobx -jest.mock('mobx-react-lite') -import { observer } from 'mobx-react-lite' -observer.mockImplementation((v) => v) - -// mock the router -jest.mock('next/router') -import { useRouter } from 'next/router' -const defaultRouterMock = () => { - const router = jest.fn() - router.query = {} - router.push = jest.fn() - router.pathname = 'logs/path' - return router -} -useRouter.mockReturnValue(defaultRouterMock()) -// mock usage flags -jest.mock('components/ui/Flag/Flag') -import Flag from 'components/ui/Flag/Flag' -Flag.mockImplementation(({ children }) => <>{children}) -jest.mock('hooks') -import { useFlag } from 'hooks' -useFlag.mockReturnValue(true) - -import { SWRConfig } from 'swr' -jest.mock('components/interfaces/Reports/PresetReport') -import PresetReport from 'components/interfaces/Reports/PresetReport' -PresetReport.mockImplementation((props) => { - const Comp = jest.requireActual('components/interfaces/Reports/PresetReport').default - // wrap with SWR to reset the cache each time - return ( - new Map(), - shouldRetryOnError: false, - }} - > - - - ) -}) - -jest.mock('hooks') -import { useProjectSubscription } from 'hooks' -useProjectSubscription = jest.fn((ref) => ({ - subscription: { - tier: { - supabase_prod_id: 'tier_free', - }, - }, -})) - -import { render, fireEvent, waitFor, screen, act } from '@testing-library/react' -import { Presets } from 'components/interfaces/Reports/Reports.types' -beforeEach(() => { - // reset mocks between tests - get.mockReset() - useRouter.mockReset() - useRouter.mockReturnValue(defaultRouterMock()) -}) - -test('static elements', async () => { - render() - await screen.findByText(/Last 7 days/) - await screen.findAllByText(/Refresh/) -}) - -test('changing date range triggers query refresh', async () => { - render() - await waitFor(() => expect(get).toBeCalled()) - get.mockReset() - const refresh = await screen.findByText(/Refresh/) - fireEvent.click(refresh) - await waitFor(() => expect(get).toBeCalled()) -}) diff --git a/studio/tests/pages/projects/reports/product-reports.test.js b/studio/tests/pages/projects/reports/product-reports.test.js new file mode 100644 index 00000000000..a30e33c1aea --- /dev/null +++ b/studio/tests/pages/projects/reports/product-reports.test.js @@ -0,0 +1,55 @@ +import dayjs from 'dayjs' +import utc from 'dayjs/plugin/utc' +dayjs.extend(utc) + +import { get, post } from 'lib/common/fetch' + +import { useRouter } from 'next/router' +import { render } from '../../../helpers' +import { fireEvent, waitFor, screen } from '@testing-library/react' +import { ApiOverviewReport } from 'pages/project/[ref]/reports/api-overview' +import { ApiBotsReport } from 'pages/project/[ref]/reports/api-bots' +import { AuthReport } from 'pages/project/[ref]/reports/auth' + +beforeEach(() => { + // reset mocks between tests + get.mockReset() + useRouter.mockReset() + useRouter.mockReturnValue({ + query: { ref: '123' }, + }) + get.mockImplementation(async (url) => { + return [{ data: [] }] + }) + post.mockResolvedValue([]) +}) + +describe.each([ + { Page: ApiOverviewReport, contains: ['API Overview'] }, + { Page: ApiBotsReport, contains: ['API Bots'] }, + { Page: AuthReport, contains: ['Auth'] }, +])('$Page rendering', ({ Page, contains }) => { + test(`contains ${contains}`, async () => { + render() + + contains.forEach((word) => { + expect(screen.findByText(word)).resolves.toBeTruthy() + }) + }) + test('static elements', async () => { + render() + await screen.findByText(/Last 7 days/) + await screen.findAllByText(/Refresh/) + }) + + test('changing date range triggers query refresh', async () => { + render() + await waitFor(() => expect(get).toBeCalled()) + get.mockReset() + const refresh = await screen.findByText(/Refresh/) + fireEvent.click(refresh) + + const calls = get.mock.calls.concat(post.mock.calls) + expect(calls.length).toBeGreaterThan(0) + }) +}) From 2c7890e4f2bde2b69c9c68179571bfe46942172c Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Mon, 12 Dec 2022 19:02:08 +0800 Subject: [PATCH 171/215] fix: update querying revalidation to reduce mutliple querying --- studio/hooks/analytics/useDbQuery.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/studio/hooks/analytics/useDbQuery.tsx b/studio/hooks/analytics/useDbQuery.tsx index 9dbbf902440..45d9236d4f9 100644 --- a/studio/hooks/analytics/useDbQuery.tsx +++ b/studio/hooks/analytics/useDbQuery.tsx @@ -21,6 +21,9 @@ const useDbQuery: UseDbQuery = (sql, params = DEFAULT_QUERY_PARAMS) => { mutate, } = useSWR(resolvedSql, async () => await meta.query(resolvedSql), { revalidateOnFocus: false, + revalidateIfStale: false, + revalidateOnReconnect: false, + dedupingInterval: 2000, }) const error = swrError || (typeof data === 'object' ? data?.error : '') From 233f023698b93177cd063ecd1e90ad2c034fbcb9 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Mon, 12 Dec 2022 20:54:42 +0800 Subject: [PATCH 172/215] feat: fixed auth daily active users query --- studio/components/interfaces/Reports/ReportWidget.tsx | 2 +- .../interfaces/Reports/Reports.constants.ts | 9 ++++----- .../interfaces/Reports/renderers/AuthRenderers.tsx | 7 +++++-- studio/pages/project/[ref]/reports/api-bots.tsx | 11 ++++++----- studio/pages/project/[ref]/reports/api-overview.tsx | 2 +- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/studio/components/interfaces/Reports/ReportWidget.tsx b/studio/components/interfaces/Reports/ReportWidget.tsx index 84d54267af2..09e03029f59 100644 --- a/studio/components/interfaces/Reports/ReportWidget.tsx +++ b/studio/components/interfaces/Reports/ReportWidget.tsx @@ -19,7 +19,7 @@ const ReportWidget: React.FC = (props) => { const router = useRouter() const { ref } = router.query return ( - +
    diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index 59ef87fd261..f4811451e16 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -167,14 +167,13 @@ export const PRESET_CONFIG: Record = { sql: ` select timestamp_trunc(timestamp, day) as timestamp, - count(distinct json_extract(f.event_message, "$.user_id")) as count -from gotrue_logs f -cross join unnest(f.metadata) as m -where json_extract(f.event_message, "$.user_id") is not null + count(distinct json_value(f.event_message, "$.user_id")) as count +from auth_logs f +where json_value(f.event_message, "$.action") = "login" group by timestamp order by - timestamp + timestamp desc `, }, cumulativeUsers: { diff --git a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx index f85ddabeece..7fe04969540 100644 --- a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx @@ -14,7 +14,7 @@ export const renderCumulativeUsers = ( ) => { + const last = props.data?.[props.data.length - 1]?.count + return (
    diff --git a/studio/pages/project/[ref]/reports/api-bots.tsx b/studio/pages/project/[ref]/reports/api-bots.tsx index 5da674c7a09..3df85b74abf 100644 --- a/studio/pages/project/[ref]/reports/api-bots.tsx +++ b/studio/pages/project/[ref]/reports/api-bots.tsx @@ -6,7 +6,10 @@ import { Presets } from 'components/interfaces/Reports/Reports.types' import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout' import { hooksFactory, usePresetReport } from 'components/interfaces/Reports/Reports.utils' import { PRESET_CONFIG } from 'components/interfaces/Reports/Reports.constants' -import { renderBotScores, renderUserAgents } from 'components/interfaces/Reports/renderers/ApiBotsRenderers' +import { + renderBotScores, + renderUserAgents, +} from 'components/interfaces/Reports/renderers/ApiBotsRenderers' import ReportWidget from 'components/interfaces/Reports/ReportWidget' export const ApiBotsReport: NextPageWithLayout = () => { @@ -16,12 +19,10 @@ export const ApiBotsReport: NextPageWithLayout = () => { const hooks = hooksFactory(ref as string, config) const userAgents = hooks.userAgents() const botScores = hooks.botScores() - const { isLoading, Layout } = usePresetReport( - [userAgents, botScores], - ) + const { isLoading, Layout } = usePresetReport([userAgents, botScores]) return ( -
    +
    { const { isLoading, Layout } = usePresetReport([statusCodes, errorRates, requestPaths]) return ( -
    +
    Date: Mon, 12 Dec 2022 20:56:54 +0800 Subject: [PATCH 173/215] fix: add no user agent text --- .../interfaces/Reports/renderers/ApiBotsRenderers.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx b/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx index 22f47142c32..9c92a616dad 100644 --- a/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx @@ -25,7 +25,11 @@ export const renderUserAgents = ( return ( - {row.user_agent} + {row.user_agent ? ( + row.user_agent + ) : ( + No user agent + )} {row.request_source} @@ -87,4 +91,4 @@ export const renderBotScores = ( } /> ) -} \ No newline at end of file +} From 608bb492240ba95b05a520182591151d410d51e3 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Mon, 12 Dec 2022 21:55:54 +0800 Subject: [PATCH 174/215] feat: add failed migration counts widget for auth report --- .../interfaces/Reports/ReportWidget.tsx | 37 +++++++++++++++++-- .../interfaces/Reports/Reports.constants.ts | 10 +++++ .../interfaces/Reports/Statistic.tsx | 36 ++++++++++++++++++ .../Reports/renderers/AuthRenderers.tsx | 14 ++++++- studio/pages/project/[ref]/reports/auth.tsx | 19 ++++++++-- 5 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 studio/components/interfaces/Reports/Statistic.tsx diff --git a/studio/components/interfaces/Reports/ReportWidget.tsx b/studio/components/interfaces/Reports/ReportWidget.tsx index 09e03029f59..2594c10c646 100644 --- a/studio/components/interfaces/Reports/ReportWidget.tsx +++ b/studio/components/interfaces/Reports/ReportWidget.tsx @@ -4,11 +4,14 @@ import Panel from 'components/ui/Panel' import { useRouter } from 'next/router' import { BaseReportParams } from './Reports.types' import { LogsEndpointParams } from '../Settings/Logs' +import * as Tooltip from '@radix-ui/react-tooltip' +import { IconHelpCircle } from '@supabase/ui' export interface ReportWidgetProps { data: T[] title: string - description: string + description?: string + tooltip?: string className?: string renderer: (props: ReportWidgetProps) => React.ReactNode params: BaseReportParams | LogsEndpointParams @@ -19,11 +22,37 @@ const ReportWidget: React.FC = (props) => { const router = useRouter() const { ref } = router.query return ( - +
    -

    {props.title}

    +
    + +

    {props.title}

    {' '} + {props?.tooltip && ( + + + + + + +
    + {props.tooltip} +
    +
    +
    + )} +

    {props.description}

    = (props) => { title="Actions..." as="span" type="text" - icon={} + icon={} >
    diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index f4811451e16..bf67eb7c019 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -162,6 +162,16 @@ export const PRESET_CONFIG: Record = { [Presets.AUTH]: { title: 'Auth', queries: { + failedMigrations: { + queryType: "logs", + sql: ` +select count(f.id) +from auth_logs +where json_value(f.event_message, "$.level") = "fatal" + and regexp_contains(f.event_message, "error executing migrations") +limit 1 +` + }, dailyActiveUsers: { queryType: 'logs', sql: ` diff --git a/studio/components/interfaces/Reports/Statistic.tsx b/studio/components/interfaces/Reports/Statistic.tsx new file mode 100644 index 00000000000..9f21c6feefc --- /dev/null +++ b/studio/components/interfaces/Reports/Statistic.tsx @@ -0,0 +1,36 @@ +import React from 'react' + +interface Props { + value: string | number + startingValue?: string | number + prefix?: string + suffix?: string + valueFormatter?: (value: Props['value']) => string +} + +const Statistic: React.FC = ({ prefix, suffix, value, startingValue }) => { + const isNumber = typeof value === 'number' && !Number.isNaN(value) + const percentageChange = + startingValue && isNumber ? Number(startingValue) / Number(value) - 1 : null + return ( +
    +
    + {prefix} + {value} + {suffix} +
    + {startingValue && ( +
    + {isNumber && percentageChange !== null && ( + + {Number(percentageChange * 100).toFixed(1)} + {percentageChange > 0 ? 'increase' : 'decrease'} + + )} +
    + )} +
    + ) +} + +export default Statistic diff --git a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx index 7fe04969540..282e4e09bd6 100644 --- a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx @@ -1,7 +1,17 @@ import { BarChart } from 'components/to-be-cleaned/Charts/ChartRenderer' import { DATETIME_FORMAT } from '../Reports.constants' import { ReportWidgetProps } from '../ReportWidget' - +import Statistic from '../Statistic' +export const renderFailedMigrations = ( + props: ReportWidgetProps<{ + timestamp: string + count: number + }> +) => { + console.log(props.data) + const count = props.data.length === 0 ? 0: props?.data?.[0]?.count + return +} export const renderCumulativeUsers = ( props: ReportWidgetProps<{ count: number @@ -15,6 +25,7 @@ export const renderCumulativeUsers = ( data={props.data} attribute="count" label="" + minimalHeader highlightedValue={total} customDateFormat={DATETIME_FORMAT} noDataMessage={'No confirmed users yet'} @@ -36,6 +47,7 @@ export const renderNewUsers = ( data={props.data} attribute="count" label="" + minimalHeader highlightedValue={sum} customDateFormat={DATETIME_FORMAT} noDataMessage={'No new user signups during this time period'} diff --git a/studio/pages/project/[ref]/reports/auth.tsx b/studio/pages/project/[ref]/reports/auth.tsx index 5e6c6781a79..42e22d60ade 100644 --- a/studio/pages/project/[ref]/reports/auth.tsx +++ b/studio/pages/project/[ref]/reports/auth.tsx @@ -11,6 +11,7 @@ import { renderDailyActiveUsers, renderNewUsers, renderCumulativeUsers, + renderFailedMigrations, } from 'components/interfaces/Reports/renderers/AuthRenderers' export const AuthReport: NextPageWithLayout = () => { @@ -21,15 +22,25 @@ export const AuthReport: NextPageWithLayout = () => { const dailyActiveUsers = hooks.dailyActiveUsers() const newUsers = hooks.newUsers() const cumulativeUsers = hooks.cumulativeUsers() + const failedMigrations = hooks.failedMigrations() const { isLoading, Layout } = usePresetReport([dailyActiveUsers, newUsers, cumulativeUsers]) return ( -
    +
    + { { Date: Mon, 12 Dec 2022 21:58:57 +0800 Subject: [PATCH 175/215] chore: remove console.logging --- studio/components/interfaces/Reports/renderers/AuthRenderers.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx index 282e4e09bd6..0444185508a 100644 --- a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx @@ -8,7 +8,6 @@ export const renderFailedMigrations = ( count: number }> ) => { - console.log(props.data) const count = props.data.length === 0 ? 0: props?.data?.[0]?.count return } From b91da2d3bd5af68ceb0baab6bad4dc4d3f8f1f1c Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Wed, 14 Dec 2022 18:52:47 +0800 Subject: [PATCH 176/215] feat: StackedBarChart implementation --- .../components/ui/Charts/Charts.constants.ts | 18 +++ studio/components/ui/Charts/Charts.types.tsx | 16 ++- studio/components/ui/Charts/Charts.utils.ts | 38 ------ studio/components/ui/Charts/Charts.utils.tsx | 118 ++++++++++++++++ .../ui/Charts/StackedBarChart.stories.tsx | 81 +++++++++++ .../components/ui/Charts/StackedBarChart.tsx | 129 ++++++++++++++++++ 6 files changed, 360 insertions(+), 40 deletions(-) delete mode 100644 studio/components/ui/Charts/Charts.utils.ts create mode 100644 studio/components/ui/Charts/Charts.utils.tsx create mode 100644 studio/components/ui/Charts/StackedBarChart.stories.tsx create mode 100644 studio/components/ui/Charts/StackedBarChart.tsx diff --git a/studio/components/ui/Charts/Charts.constants.ts b/studio/components/ui/Charts/Charts.constants.ts index d423e9129e9..f9cff43f861 100644 --- a/studio/components/ui/Charts/Charts.constants.ts +++ b/studio/components/ui/Charts/Charts.constants.ts @@ -9,6 +9,24 @@ export const CHART_COLORS = { GREEN_2: 'var(--colors-brand6)', } +// refer to packages/ui/radix-colors.js for full list of colors +export const STACK_COLORS = [ + ['brand', 8], + ['mint', 8], + ['blue', 8], + ['sky', 7], + ['bronze', 8], + ['violet', 7], + ['indigo', 8], + ['tomato', 7], + ['orange', 8], + ['olive', 8], +].map(([color, n]) => ({ + lighter: `var(--colors-${color}${(n as number) - 1})`, + base: `var(--colors-${color}${n})`, + darker: `var(--colors-${color}${(n as number) + 1})`, +})) + export const USAGE_COLORS = { 200: 'var(--colors-brand9)', 201: 'var(--colors-brand8)', diff --git a/studio/components/ui/Charts/Charts.types.tsx b/studio/components/ui/Charts/Charts.types.tsx index 239d1f29368..3332fc441ae 100644 --- a/studio/components/ui/Charts/Charts.types.tsx +++ b/studio/components/ui/Charts/Charts.types.tsx @@ -1,3 +1,5 @@ +import { AreaProps } from 'recharts' + export interface CommonChartProps extends Pick< HeaderType, @@ -9,8 +11,18 @@ export interface CommonChartProps | 'minimalHeader' | 'displayDateInUtc' > { - title?: string; + title?: string className?: string + isLoading?: boolean + size?: 'small' | 'normal' | 'large' +} + +export interface StackedChartProps extends CommonChartProps { + xAxisKey: string + xAxisFormatAsDate?: boolean + dateFormat?: string + yAxisKey: string + stackKey: string } export type HeaderType = { @@ -18,7 +30,7 @@ export type HeaderType = { focus: number | null format?: string highlightedValue?: number | string - highlightedLabel?: string + highlightedLabel?: string data: D[] customDateFormat?: string label: string diff --git a/studio/components/ui/Charts/Charts.utils.ts b/studio/components/ui/Charts/Charts.utils.ts deleted file mode 100644 index 8683067c1b9..00000000000 --- a/studio/components/ui/Charts/Charts.utils.ts +++ /dev/null @@ -1,38 +0,0 @@ - -/** - * Auto formats a number to a default precision if it is a float - * - * @example - * numberFormatter(123) // "123" - * numberFormatter(123.123) // "123.12" - * numberFormatter(123, 2) // "123.00" - */ -export const numberFormatter = (num: number, precision = 2) => isFloat(num) ? precisionFormatter(num, precision) : String(num); - - -/** - * Tests if a number is a float. - * - * @example - * isFloat(123) // false - * isFloat(123.123) // true - */ -export const isFloat = (num: number) => String(num).includes(".") - - -/** - * Formats a number to a particular precision. - * - * @example - * precisionFormatter(123, 2) // "123.00" - * precisionFormatter(123.123, 2) // "123.12" - */ -export const precisionFormatter = (num: number, precision: number): string => { - if (isFloat(num)) { - const [head, tail] = String(num).split(".") - return head + "." + tail.slice(0, precision) - } else { - // pad int with 0 - return String(num) + "." + ("0".repeat(precision)) - } -} \ No newline at end of file diff --git a/studio/components/ui/Charts/Charts.utils.tsx b/studio/components/ui/Charts/Charts.utils.tsx new file mode 100644 index 00000000000..687603d651e --- /dev/null +++ b/studio/components/ui/Charts/Charts.utils.tsx @@ -0,0 +1,118 @@ +import { DATETIME_FORMAT } from 'components/interfaces/Reports/Reports.constants' +import dayjs from 'dayjs' +import React, { useMemo } from 'react' +import { ResponsiveContainer } from 'recharts' +import { DateTimeFormats } from './Charts.constants' +import { CommonChartProps, StackedChartProps } from './Charts.types' + +/** + * Auto formats a number to a default precision if it is a float + * + * @example + * numberFormatter(123) // "123" + * numberFormatter(123.123) // "123.12" + * numberFormatter(123, 2) // "123.00" + */ +export const numberFormatter = (num: number, precision = 2) => + isFloat(num) ? precisionFormatter(num, precision) : String(num) + +/** + * Tests if a number is a float. + * + * @example + * isFloat(123) // false + * isFloat(123.123) // true + */ +export const isFloat = (num: number) => String(num).includes('.') + +/** + * Formats a number to a particular precision. + * + * @example + * precisionFormatter(123, 2) // "123.00" + * precisionFormatter(123.123, 2) // "123.12" + */ +export const precisionFormatter = (num: number, precision: number): string => { + if (isFloat(num)) { + const [head, tail] = String(num).split('.') + return head + '.' + tail.slice(0, precision) + } else { + // pad int with 0 + return String(num) + '.' + '0'.repeat(precision) + } +} + +export const timestampFormatter = (value: string, format: string= DateTimeFormats.FULL) => dayjs(value).format(format) + +/** + * Hook to create common wrapping components, perform data transformations + * returns a Container component and the minHeight set + */ +export const useChartSize = (size: CommonChartProps['size'] = 'normal') => { + const minHeight = { small: 120, normal: 160, large: 280 }[size] + const Container: React.FC = useMemo( + () => + ({ children }) => + ( + + {children as JSX.Element} + + ), + [size] + ) + return { + Container, + minHeight, + } +} + +/** + * Transforms data points into a stacked data structure that can be consumed by recharts + */ +export const useStacked = ({ + data, + xAxisKey, + yAxisKey, + stackKey, + variant = 'values', +}: Pick, 'xAxisKey' | 'yAxisKey' | 'stackKey'> & { + variant?: 'values' | 'percentages' +} & Pick>, 'data'>) => { + const stackedData = useMemo(() => { + if (!data) return [] + const mapping = data.reduce((acc, datum) => { + const x = datum[xAxisKey] + const y = datum[yAxisKey] + const s = datum[stackKey] + if (!acc[x]) { + acc[x] = {} + } + + acc[x][s] = y + return acc + }, {} as Record>) + + const flattened = Object.entries(mapping).map(([x, sMap]) => { + let map = sMap + if (variant === 'percentages') { + const sum = Object.values(sMap).reduce((acc, value) => acc + value, 0) + map = Object.entries(sMap).reduce((acc, [key, value]) => { + acc[key] = value !== 0 ? value / sum : 0 + return acc + }, {} as any) + } + return { + ...map, + [xAxisKey]: x, + } + }) + return flattened + }, [JSON.stringify(data)]) + const dataKeys = useMemo(() => { + return Object.keys(stackedData[0] || {}) + .filter((k) => k !== xAxisKey && k !== yAxisKey) + .sort() + }, [JSON.stringify(stackedData[0] || {})]) + + return { dataKeys, stackedData } +} diff --git a/studio/components/ui/Charts/StackedBarChart.stories.tsx b/studio/components/ui/Charts/StackedBarChart.stories.tsx new file mode 100644 index 00000000000..3f3feabebb3 --- /dev/null +++ b/studio/components/ui/Charts/StackedBarChart.stories.tsx @@ -0,0 +1,81 @@ +import React from 'react' +import { ComponentStory, ComponentMeta } from '@storybook/react' +import dayjs from 'dayjs' +import Chart from './StackedBarChart' +import { StackedChartProps } from './Charts.types' +import { STACK_COLORS } from './Charts.constants' + +export default { + title: 'Charts/StackedBarChart', +} as ComponentMeta + +const DATA = [290, 430, 649, 422, 321].flatMap((value, index) => [ + { + count: value, + level: 'error', + timestamp: dayjs().subtract(index, 'day').toISOString(), + }, + { + count: value * 2, + level: 'info', + timestamp: dayjs().subtract(index, 'day').toISOString(), + }, + { + count: value * 1.3, + level: 'warn', + timestamp: dayjs().subtract(index, 'day').toISOString(), + }, +]) + +const PROPS = { + title: 'Memory usage', + xAxisKey: 'timestamp', + yAxisKey: 'count', + stackKey: 'level', + data: DATA, +} +export const StackedBarChart = () => ( +
    + Stack Colors +
    + {STACK_COLORS.map((c) => ( +
    + ))} +
    + + Examples +
    + {[ + { title: 'Normal', props: {} }, + { title: 'No title', props: { title: undefined } }, + { title: 'Minimal Header', props: { minimalHeader: true } }, + { title: 'Percentage Bars', props: { variant: 'percentages' as const } }, + { + title: 'Formatter - Auto 2 Precision for floats', + props: { data: DATA.map((d) => ({ ...d, count: d.count + 5.12 })) }, + }, + { title: 'UTC Dates', props: { displayDateInUtc: true } }, + { title: 'Custom date format', props: { customDateFormat: 'HH:mm' } }, + { title: 'Value format (c suffix)', props: { format: 'c' } }, + { title: 'Minimal Value format', props: { minimalHeader: true, format: 'c' } }, + { title: 'No data', props: { data: [] } }, + ].map(({ title, props }) => ( +
    + {title} +
    + +
    +
    + ))} +
    +
    +) + +const ExampleName: React.FC = ({ children }) => ( +

    + {children} +

    +) diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx new file mode 100644 index 00000000000..126e1eb2d0c --- /dev/null +++ b/studio/components/ui/Charts/StackedBarChart.tsx @@ -0,0 +1,129 @@ +import dayjs from 'dayjs' +import { useState } from 'react' +import { BarChart, Bar, XAxis, Tooltip, Legend, Cell } from 'recharts' +import ChartHeader from './ChartHeader' +import { CHART_COLORS, STACK_COLORS, DateTimeFormats } from './Charts.constants' +import { CommonChartProps, StackedChartProps } from './Charts.types' +import { timestampFormatter, useChartSize, useStacked } from './Charts.utils' +import { precisionFormatter } from './Charts.utils' +import NoDataPlaceholder from './NoDataPlaceholder' + +interface Props extends CommonChartProps { + xAxisKey: string + yAxisKey: string + stackKey: string + onBarClick?: () => void + variant?: 'values' | 'percentages' + xAxisFormatAsDate?: boolean +} +const StackedBarChart: React.FC = ({ + size, + data, + xAxisKey, + stackKey, + yAxisKey, + customDateFormat = DateTimeFormats.FULL, + title, + format, + minimalHeader = false, + onBarClick, + variant, + xAxisFormatAsDate = true, +}) => { + const { Container } = useChartSize(size) + const { dataKeys, stackedData } = useStacked({ data, xAxisKey, stackKey, yAxisKey, variant }) + const [focusDataIndex, setFocusDataIndex] = useState(null) + if (data.length === 0) return + return ( +
    + + + { + console.log(e) + if (e.activeTooltipIndex !== focusDataIndex) { + setFocusDataIndex(e.activeTooltipIndex) + } + }} + onMouseLeave={() => setFocusDataIndex(null)} + > + + + + {/* */} + {/* */} + {dataKeys.map((datum, stackIndex) => ( + + {stackedData?.map((_entry: unknown, index: any) => ( + + ))} + + ))} + timestampFormatter(label) : undefined} + formatter={(value: number) => { + if (variant === 'percentages') { + return precisionFormatter(value * 100, 1) + '%' + } + return value + }} + cursor={false} + labelClassName="text-white" + contentStyle={{ backgroundColor: '#444444', borderColor: '#444444', fontSize: '12px' }} + wrapperClassName="bg-gray-600 rounded" + /> + + + {/* {console.log(data)} */} + {stackedData && stackedData[0] && ( +
    + {dayjs(stackedData[0][xAxisKey]).format(customDateFormat)} + {dayjs(stackedData[data?.length - 1]?.[xAxisKey]).format(customDateFormat)} +
    + )} +
    + ) +} + +export default StackedBarChart From 4c7160d07f6b172eea2589c66a550063607f9b2e Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Thu, 15 Dec 2022 14:24:21 +0800 Subject: [PATCH 177/215] feat: add utc datetime rendering for StackedBarChart --- studio/components/ui/Charts/Charts.utils.tsx | 21 +++++++++++++- .../components/ui/Charts/StackedBarChart.tsx | 29 ++++++++++++++----- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/studio/components/ui/Charts/Charts.utils.tsx b/studio/components/ui/Charts/Charts.utils.tsx index 687603d651e..ea260c5ff57 100644 --- a/studio/components/ui/Charts/Charts.utils.tsx +++ b/studio/components/ui/Charts/Charts.utils.tsx @@ -4,6 +4,8 @@ import React, { useMemo } from 'react' import { ResponsiveContainer } from 'recharts' import { DateTimeFormats } from './Charts.constants' import { CommonChartProps, StackedChartProps } from './Charts.types' +import utc from 'dayjs/plugin/utc' +dayjs.extend(utc) /** * Auto formats a number to a default precision if it is a float @@ -42,7 +44,24 @@ export const precisionFormatter = (num: number, precision: number): string => { } } -export const timestampFormatter = (value: string, format: string= DateTimeFormats.FULL) => dayjs(value).format(format) +/** + * Formats a timestamp. + * Optionally formats the string to UTC + * @param value + * @param format + * @param utc + * @returns + */ +export const timestampFormatter = ( + value: string, + format: string = DateTimeFormats.FULL, + utc: boolean = false +) => { + if (utc) { + return dayjs.utc(value).format(format) + } + return dayjs(value).format(format) +} /** * Hook to create common wrapping components, perform data transformations diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx index 126e1eb2d0c..be4771fa158 100644 --- a/studio/components/ui/Charts/StackedBarChart.tsx +++ b/studio/components/ui/Charts/StackedBarChart.tsx @@ -1,13 +1,11 @@ -import dayjs from 'dayjs' import { useState } from 'react' import { BarChart, Bar, XAxis, Tooltip, Legend, Cell } from 'recharts' import ChartHeader from './ChartHeader' import { CHART_COLORS, STACK_COLORS, DateTimeFormats } from './Charts.constants' -import { CommonChartProps, StackedChartProps } from './Charts.types' +import { CommonChartProps } from './Charts.types' import { timestampFormatter, useChartSize, useStacked } from './Charts.utils' import { precisionFormatter } from './Charts.utils' import NoDataPlaceholder from './NoDataPlaceholder' - interface Props extends CommonChartProps { xAxisKey: string yAxisKey: string @@ -15,6 +13,7 @@ interface Props extends CommonChartProps { onBarClick?: () => void variant?: 'values' | 'percentages' xAxisFormatAsDate?: boolean + displayDateInUtc?: boolean } const StackedBarChart: React.FC = ({ size, @@ -29,6 +28,7 @@ const StackedBarChart: React.FC = ({ onBarClick, variant, xAxisFormatAsDate = true, + displayDateInUtc, }) => { const { Container } = useChartSize(size) const { dataKeys, stackedData } = useStacked({ data, xAxisKey, stackKey, yAxisKey, variant }) @@ -101,7 +101,11 @@ const StackedBarChart: React.FC = ({ ))} timestampFormatter(label) : undefined} + labelFormatter={ + xAxisFormatAsDate + ? (label) => timestampFormatter(label, customDateFormat, displayDateInUtc) + : undefined + } formatter={(value: number) => { if (variant === 'percentages') { return precisionFormatter(value * 100, 1) + '%' @@ -115,11 +119,22 @@ const StackedBarChart: React.FC = ({ /> - {/* {console.log(data)} */} {stackedData && stackedData[0] && (
    - {dayjs(stackedData[0][xAxisKey]).format(customDateFormat)} - {dayjs(stackedData[data?.length - 1]?.[xAxisKey]).format(customDateFormat)} + + {timestampFormatter( + stackedData[0][xAxisKey] as string, + customDateFormat, + displayDateInUtc + )} + + + {timestampFormatter( + stackedData[stackedData?.length - 1][xAxisKey] as string, + customDateFormat, + displayDateInUtc + )} +
    )}
    From c2116126c8808416a0b016a3c9f12f9deb25a22d Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Thu, 15 Dec 2022 14:26:26 +0800 Subject: [PATCH 178/215] feat: add optoinal legend hiding --- .../ui/Charts/StackedBarChart.stories.tsx | 1 + .../components/ui/Charts/StackedBarChart.tsx | 20 +++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/studio/components/ui/Charts/StackedBarChart.stories.tsx b/studio/components/ui/Charts/StackedBarChart.stories.tsx index 3f3feabebb3..12e12d2c2a6 100644 --- a/studio/components/ui/Charts/StackedBarChart.stories.tsx +++ b/studio/components/ui/Charts/StackedBarChart.stories.tsx @@ -49,6 +49,7 @@ export const StackedBarChart = () => ( { title: 'Normal', props: {} }, { title: 'No title', props: { title: undefined } }, { title: 'Minimal Header', props: { minimalHeader: true } }, + { title: 'Hide Legend', props: { hideLegend: true } }, { title: 'Percentage Bars', props: { variant: 'percentages' as const } }, { title: 'Formatter - Auto 2 Precision for floats', diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx index be4771fa158..0141b85bd2e 100644 --- a/studio/components/ui/Charts/StackedBarChart.tsx +++ b/studio/components/ui/Charts/StackedBarChart.tsx @@ -14,6 +14,7 @@ interface Props extends CommonChartProps { variant?: 'values' | 'percentages' xAxisFormatAsDate?: boolean displayDateInUtc?: boolean + hideLegend?: boolean } const StackedBarChart: React.FC = ({ size, @@ -29,6 +30,7 @@ const StackedBarChart: React.FC = ({ variant, xAxisFormatAsDate = true, displayDateInUtc, + hideLegend = false, }) => { const { Container } = useChartSize(size) const { dataKeys, stackedData } = useStacked({ data, xAxisKey, stackKey, yAxisKey, variant }) @@ -61,14 +63,14 @@ const StackedBarChart: React.FC = ({ }} onMouseLeave={() => setFocusDataIndex(null)} > - - + {!hideLegend && ( + + )} = ({ axisLine={{ stroke: CHART_COLORS.AXIS }} tickLine={{ stroke: CHART_COLORS.AXIS }} /> - {/* */} - {/* */} {dataKeys.map((datum, stackIndex) => ( Date: Thu, 15 Dec 2022 14:29:27 +0800 Subject: [PATCH 179/215] fix: tweak position of legend --- studio/components/ui/Charts/StackedBarChart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx index 0141b85bd2e..099e96777ae 100644 --- a/studio/components/ui/Charts/StackedBarChart.tsx +++ b/studio/components/ui/Charts/StackedBarChart.tsx @@ -65,7 +65,7 @@ const StackedBarChart: React.FC = ({ > {!hideLegend && ( Date: Thu, 15 Dec 2022 14:32:31 +0800 Subject: [PATCH 180/215] feat: added tooltip formatting for suffix --- studio/components/ui/Charts/StackedBarChart.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx index 099e96777ae..ed8b0ea51d9 100644 --- a/studio/components/ui/Charts/StackedBarChart.tsx +++ b/studio/components/ui/Charts/StackedBarChart.tsx @@ -107,10 +107,12 @@ const StackedBarChart: React.FC = ({ : undefined } formatter={(value: number) => { + let val: string = String(value) if (variant === 'percentages') { - return precisionFormatter(value * 100, 1) + '%' + val = precisionFormatter(value * 100, 1) + '%' } - return value + val += (format || '') + return val }} cursor={false} labelClassName="text-white" From 2d5602067fc773aa537ee76f3882dcf51508b476 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 16 Dec 2022 15:16:37 +0800 Subject: [PATCH 181/215] feat: add value rendering in tooltip alongside percentage render, add testing for stacked chart utils --- package-lock.json | 75 ++++++++++++++++++- studio/components/ui/Charts/Charts.utils.tsx | 39 ++++++---- .../ui/Charts/StackedBarChart.stories.tsx | 8 +- .../components/ui/Charts/StackedBarChart.tsx | 31 +++++--- studio/package.json | 5 +- .../components/ui/Charts/Charts.utils.test.ts | 60 ++++++++++++--- 6 files changed, 172 insertions(+), 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index d002b6210bb..2f08bafa685 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9827,6 +9827,36 @@ "react-dom": "<18.0.0" } }, + "node_modules/@testing-library/react-hooks": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz", + "integrity": "sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "react-error-boundary": "^3.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0", + "react": "^16.9.0 || ^17.0.0", + "react-dom": "^16.9.0 || ^17.0.0", + "react-test-renderer": "^16.9.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-test-renderer": { + "optional": true + } + } + }, "node_modules/@testing-library/user-event": { "version": "13.5.0", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", @@ -29842,6 +29872,22 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, + "node_modules/react-error-boundary": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", + "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-fast-compare": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", @@ -37621,8 +37667,9 @@ "@storybook/react": "^6.4.19", "@storybook/testing-library": "^0.0.9", "@tailwindcss/typography": "^0.5.2", - "@testing-library/dom": "^8.11.1", - "@testing-library/react": "^12.1.2", + "@testing-library/dom": "^8.19.0", + "@testing-library/react": "^12.1.5", + "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^13.5.0", "@types/blueimp-md5": "^2.18.0", "@types/crypto-js": "^4.0.2", @@ -44568,6 +44615,16 @@ "@types/react-dom": "<18.0.0" } }, + "@testing-library/react-hooks": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz", + "integrity": "sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5", + "react-error-boundary": "^3.1.0" + } + }, "@testing-library/user-event": { "version": "13.5.0", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", @@ -60519,6 +60576,15 @@ } } }, + "react-error-boundary": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", + "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5" + } + }, "react-fast-compare": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", @@ -63325,8 +63391,9 @@ "@tailwindcss/typography": "^0.5.2", "@tanstack/react-query": "^4.16.1", "@tanstack/react-query-devtools": "^4.16.1", - "@testing-library/dom": "^8.11.1", - "@testing-library/react": "^12.1.2", + "@testing-library/dom": "^8.19.0", + "@testing-library/react": "^12.1.5", + "@testing-library/react-hooks": "*", "@testing-library/user-event": "^13.5.0", "@types/blueimp-md5": "^2.18.0", "@types/crypto-js": "^4.0.2", diff --git a/studio/components/ui/Charts/Charts.utils.tsx b/studio/components/ui/Charts/Charts.utils.tsx index ea260c5ff57..af6c33d77db 100644 --- a/studio/components/ui/Charts/Charts.utils.tsx +++ b/studio/components/ui/Charts/Charts.utils.tsx @@ -111,20 +111,10 @@ export const useStacked = ({ return acc }, {} as Record>) - const flattened = Object.entries(mapping).map(([x, sMap]) => { - let map = sMap - if (variant === 'percentages') { - const sum = Object.values(sMap).reduce((acc, value) => acc + value, 0) - map = Object.entries(sMap).reduce((acc, [key, value]) => { - acc[key] = value !== 0 ? value / sum : 0 - return acc - }, {} as any) - } - return { - ...map, - [xAxisKey]: x, - } - }) + const flattened = Object.entries(mapping).map(([x, sMap]) => ({ + ...sMap, + [xAxisKey]: Number.isNaN(Number(x)) ? x : Number(x), + })) return flattened }, [JSON.stringify(data)]) const dataKeys = useMemo(() => { @@ -133,5 +123,24 @@ export const useStacked = ({ .sort() }, [JSON.stringify(stackedData[0] || {})]) - return { dataKeys, stackedData } + const percentagesStackedData = useMemo(() => { + if (variant !== 'percentages') return + + return stackedData.map((stack) => { + const entries = Object.entries(stack) as Array<[string, number]> + let map + const sum = entries + .filter(([key, _value]) => dataKeys.includes(key)) + .reduce((acc, [_key, value]) => acc + value, 0) + map = entries.reduce((acc, [key, value]) => { + if (!dataKeys.includes(key)) { + return { ...acc, [key]: value } + } + return { ...acc, [key]: value !== 0 ? value / sum : 0 } + }, {} as any) + return map + }) + }, [JSON.stringify(stackedData)]) + + return { dataKeys, stackedData, percentagesStackedData } } diff --git a/studio/components/ui/Charts/StackedBarChart.stories.tsx b/studio/components/ui/Charts/StackedBarChart.stories.tsx index 12e12d2c2a6..a6703a51727 100644 --- a/studio/components/ui/Charts/StackedBarChart.stories.tsx +++ b/studio/components/ui/Charts/StackedBarChart.stories.tsx @@ -36,10 +36,10 @@ const PROPS = { } export const StackedBarChart = () => (
    - Stack Colors + Default Stacked Chart Colors
    {STACK_COLORS.map((c) => ( -
    +
    ))}
    @@ -51,6 +51,10 @@ export const StackedBarChart = () => ( { title: 'Minimal Header', props: { minimalHeader: true } }, { title: 'Hide Legend', props: { hideLegend: true } }, { title: 'Percentage Bars', props: { variant: 'percentages' as const } }, + { + title: 'Percentage Bars with suffix', + props: { variant: 'percentages' as const, format: 'mem' }, + }, { title: 'Formatter - Auto 2 Precision for floats', props: { data: DATA.map((d) => ({ ...d, count: d.count + 5.12 })) }, diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx index ed8b0ea51d9..8f389778d0b 100644 --- a/studio/components/ui/Charts/StackedBarChart.tsx +++ b/studio/components/ui/Charts/StackedBarChart.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import { BarChart, Bar, XAxis, Tooltip, Legend, Cell } from 'recharts' +import { BarChart, Bar, XAxis, Tooltip, Legend, Cell, TooltipProps } from 'recharts' import ChartHeader from './ChartHeader' import { CHART_COLORS, STACK_COLORS, DateTimeFormats } from './Charts.constants' import { CommonChartProps } from './Charts.types' @@ -33,7 +33,13 @@ const StackedBarChart: React.FC = ({ hideLegend = false, }) => { const { Container } = useChartSize(size) - const { dataKeys, stackedData } = useStacked({ data, xAxisKey, stackKey, yAxisKey, variant }) + const { dataKeys, stackedData, percentagesStackedData } = useStacked({ + data, + xAxisKey, + stackKey, + yAxisKey, + variant, + }) const [focusDataIndex, setFocusDataIndex] = useState(null) if (data.length === 0) return return ( @@ -46,7 +52,7 @@ const StackedBarChart: React.FC = ({ /> = ({ className="cursor-pointer overflow-visible" // mouse hover focusing logic onMouseMove={(e: any) => { - console.log(e) if (e.activeTooltipIndex !== focusDataIndex) { setFocusDataIndex(e.activeTooltipIndex) } @@ -106,18 +111,22 @@ const StackedBarChart: React.FC = ({ ? (label) => timestampFormatter(label, customDateFormat, displayDateInUtc) : undefined } - formatter={(value: number) => { - let val: string = String(value) - if (variant === 'percentages') { - val = precisionFormatter(value * 100, 1) + '%' + formatter={(value: number, name: string, props: any) => { + const suffix = format || '' + if (variant === 'percentages' && percentagesStackedData) { + const index = percentagesStackedData.findIndex( + (pStack) => pStack === props.payload! + ) + const val = stackedData[index][name] + const percentage = precisionFormatter(value * 100, 1) + '%' + return `${percentage} (${val}${suffix})` } - val += (format || '') - return val + return String(value) + suffix }} cursor={false} labelClassName="text-white" contentStyle={{ backgroundColor: '#444444', borderColor: '#444444', fontSize: '12px' }} - wrapperClassName="bg-gray-600 rounded" + wrapperClassName="bg-gray-600 rounded min-w-md" /> diff --git a/studio/package.json b/studio/package.json index 7a39cefd0d6..547db163b08 100644 --- a/studio/package.json +++ b/studio/package.json @@ -103,8 +103,9 @@ "@storybook/react": "^6.4.19", "@storybook/testing-library": "^0.0.9", "@tailwindcss/typography": "^0.5.2", - "@testing-library/dom": "^8.11.1", - "@testing-library/react": "^12.1.2", + "@testing-library/dom": "^8.19.0", + "@testing-library/react": "^12.1.5", + "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^13.5.0", "@types/blueimp-md5": "^2.18.0", "@types/crypto-js": "^4.0.2", diff --git a/studio/tests/components/ui/Charts/Charts.utils.test.ts b/studio/tests/components/ui/Charts/Charts.utils.test.ts index 071f19f02d9..4da9e749668 100644 --- a/studio/tests/components/ui/Charts/Charts.utils.test.ts +++ b/studio/tests/components/ui/Charts/Charts.utils.test.ts @@ -1,16 +1,52 @@ -import { isFloat, numberFormatter, precisionFormatter } from "components/ui/Charts/Charts.utils" +import { + isFloat, + numberFormatter, + precisionFormatter, + useStacked, +} from 'components/ui/Charts/Charts.utils' +import { renderHook } from '@testing-library/react-hooks' - -test("isFloat", () => { - expect(isFloat(123)).toBe(false) - expect(isFloat(123.123)).toBe(true) +test('isFloat', () => { + expect(isFloat(123)).toBe(false) + expect(isFloat(123.123)).toBe(true) }) -test("numberFormatter", () => { - expect(numberFormatter(123)).toBe("123") - expect(numberFormatter(123.123)).toBe("123.12") +test('numberFormatter', () => { + expect(numberFormatter(123)).toBe('123') + expect(numberFormatter(123.123)).toBe('123.12') }) -test("precisionFormatter", () => { - expect(precisionFormatter(123, 1)).toBe("123.0") - expect(precisionFormatter(123.12345, 4)).toBe("123.1234") -}) \ No newline at end of file +test('precisionFormatter', () => { + expect(precisionFormatter(123, 1)).toBe('123.0') + expect(precisionFormatter(123.12345, 4)).toBe('123.1234') +}) + +test('useStacked', () => { + const { result } = renderHook(() => + useStacked({ + data: [ + { label: 'a', x: 1, y: 2 }, + { label: 'b', x: 1, y: 3 }, + ] as any, + xAxisKey: 'x', + yAxisKey: 'y', + stackKey: 'label', + variant: 'percentages', + }) + ) + expect(result.current).toMatchObject({ + stackedData: [ + { + a: 2, + b: 3, + x: 1, + }, + ], + percentagesStackedData: [ + { + a: 2 / 5, + b: 3 / 5, + x: 1, + }, + ], + }) +}) From de105ac4ecf51247a1e90e8e61f5804db95c7fa3 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 16 Dec 2022 15:47:50 +0800 Subject: [PATCH 182/215] feat: add sparkline chart --- studio/components/ui/Charts/Charts.utils.tsx | 11 +++- .../ui/Charts/Sparkline.stories.tsx | 54 +++++++++++++++++++ studio/components/ui/Charts/Sparkline.tsx | 46 ++++++++++++++++ 3 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 studio/components/ui/Charts/Sparkline.stories.tsx create mode 100644 studio/components/ui/Charts/Sparkline.tsx diff --git a/studio/components/ui/Charts/Charts.utils.tsx b/studio/components/ui/Charts/Charts.utils.tsx index af6c33d77db..ce988a2d86b 100644 --- a/studio/components/ui/Charts/Charts.utils.tsx +++ b/studio/components/ui/Charts/Charts.utils.tsx @@ -67,8 +67,15 @@ export const timestampFormatter = ( * Hook to create common wrapping components, perform data transformations * returns a Container component and the minHeight set */ -export const useChartSize = (size: CommonChartProps['size'] = 'normal') => { - const minHeight = { small: 120, normal: 160, large: 280 }[size] +export const useChartSize = ( + size: CommonChartProps['size'] = 'normal', + sizeMap: { + small: number + normal: number + large: number + } = { small: 120, normal: 160, large: 280 } +) => { + const minHeight = sizeMap[size] const Container: React.FC = useMemo( () => ({ children }) => diff --git a/studio/components/ui/Charts/Sparkline.stories.tsx b/studio/components/ui/Charts/Sparkline.stories.tsx new file mode 100644 index 00000000000..e341ff7b86a --- /dev/null +++ b/studio/components/ui/Charts/Sparkline.stories.tsx @@ -0,0 +1,54 @@ +import React from 'react' +import { ComponentStory, ComponentMeta } from '@storybook/react' +import dayjs from 'dayjs' +import Chart from './Sparkline' +import { StackedChartProps } from './Charts.types' +import { STACK_COLORS } from './Charts.constants' + +export default { + title: 'Charts/Sparkline', +} as ComponentMeta + +const DATA = [290, 430, 649, 422, 321].flatMap((value, index) => [ + { + count: value, + level: 'error', + timestamp: dayjs().subtract(index, 'day').toISOString(), + }, +]) + +const PROPS = { + title: 'Memory usage', + xAxisKey: 'timestamp', + yAxisKey: 'count', + data: DATA, +} +export const Sparkline = () => ( +
    + Examples +
    + {[ + { title: 'Small', props: { size: 'small' as const } }, + { title: 'Normal', props: {} }, + { title: 'Large', props: { size: 'large' as const } }, + { title: 'No data', props: { data: [] } }, + ].map(({ title, props }) => ( +
    + {title} +
    + +
    +
    + ))} +
    +
    +) + +const ExampleName: React.FC = ({ children }) => ( +

    + {children} +

    +) diff --git a/studio/components/ui/Charts/Sparkline.tsx b/studio/components/ui/Charts/Sparkline.tsx new file mode 100644 index 00000000000..855a9f86383 --- /dev/null +++ b/studio/components/ui/Charts/Sparkline.tsx @@ -0,0 +1,46 @@ +import { useState } from 'react' +import { LineChart, Bar, XAxis, Tooltip, Legend, Cell, TooltipProps, Line } from 'recharts' +import ChartHeader from './ChartHeader' +import { CHART_COLORS, STACK_COLORS, DateTimeFormats } from './Charts.constants' +import { CommonChartProps } from './Charts.types' +import { timestampFormatter, useChartSize, useStacked } from './Charts.utils' +import { precisionFormatter } from './Charts.utils' +import NoDataPlaceholder from './NoDataPlaceholder' +interface Props extends CommonChartProps { + xAxisKey: string + yAxisKey: string +} +const Sparkline: React.FC = ({ size, data, xAxisKey, yAxisKey }) => { + const { Container } = useChartSize(size, { + small: 50, + normal: 90, + large: 140, + }) + if (data.length === 0) return null + return ( + + + + + + + ) +} + +export default Sparkline From 7796598897678d7066d91934f88a58b10e9179e0 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 16 Dec 2022 16:36:39 +0800 Subject: [PATCH 183/215] feat: added sign up provider chart, tweaked stacked chart colors --- .../interfaces/Reports/Reports.constants.ts | 13 ++++++ .../Reports/renderers/AuthRenderers.tsx | 22 +++++++++- .../components/ui/Charts/Charts.constants.ts | 17 ++++---- .../ui/Charts/StackedBarChart.stories.tsx | 1 + .../components/ui/Charts/StackedBarChart.tsx | 18 ++++---- studio/pages/project/[ref]/reports/auth.tsx | 41 ++++++++++++++----- 6 files changed, 84 insertions(+), 28 deletions(-) diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index bf67eb7c019..187bb9a7543 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -162,6 +162,19 @@ export const PRESET_CONFIG: Record = { [Presets.AUTH]: { title: 'Auth', queries: { + signUpProviders: { + queryType: "db", + sql: ` +select + sum(count(u.id)) over (order by date_trunc('day', u.created_at)) as count, + date_trunc('day', u.created_at) as timestamp, + i.provider as provider +from auth.users u +join auth.identities as i on u.id = i.user_id +group by timestamp, provider +order by timestamp desc + ` + }, failedMigrations: { queryType: "logs", sql: ` diff --git a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx index 0444185508a..f69dcc88e10 100644 --- a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx @@ -1,14 +1,34 @@ import { BarChart } from 'components/to-be-cleaned/Charts/ChartRenderer' +import StackedBarChart from 'components/ui/Charts/StackedBarChart' import { DATETIME_FORMAT } from '../Reports.constants' import { ReportWidgetProps } from '../ReportWidget' import Statistic from '../Statistic' + +export const renderSignUpProviders = ( + props: ReportWidgetProps<{ + timestamp: string + provider: string + count: number + }> +) => { + return ( + + ) +} export const renderFailedMigrations = ( props: ReportWidgetProps<{ timestamp: string count: number }> ) => { - const count = props.data.length === 0 ? 0: props?.data?.[0]?.count + const count = props.data.length === 0 ? 0 : props?.data?.[0]?.count return } export const renderCumulativeUsers = ( diff --git a/studio/components/ui/Charts/Charts.constants.ts b/studio/components/ui/Charts/Charts.constants.ts index f9cff43f861..7aa3a6de752 100644 --- a/studio/components/ui/Charts/Charts.constants.ts +++ b/studio/components/ui/Charts/Charts.constants.ts @@ -11,16 +11,13 @@ export const CHART_COLORS = { // refer to packages/ui/radix-colors.js for full list of colors export const STACK_COLORS = [ - ['brand', 8], - ['mint', 8], - ['blue', 8], - ['sky', 7], - ['bronze', 8], - ['violet', 7], - ['indigo', 8], - ['tomato', 7], - ['orange', 8], - ['olive', 8], + ['brand', 9], + ['mint', 9], + ['blue', 9], + ['sky', 9], + ['lime', 9], + ['yellow', 9], + ['orange', 9], ].map(([color, n]) => ({ lighter: `var(--colors-${color}${(n as number) - 1})`, base: `var(--colors-${color}${n})`, diff --git a/studio/components/ui/Charts/StackedBarChart.stories.tsx b/studio/components/ui/Charts/StackedBarChart.stories.tsx index a6703a51727..2e0289aa158 100644 --- a/studio/components/ui/Charts/StackedBarChart.stories.tsx +++ b/studio/components/ui/Charts/StackedBarChart.stories.tsx @@ -50,6 +50,7 @@ export const StackedBarChart = () => ( { title: 'No title', props: { title: undefined } }, { title: 'Minimal Header', props: { minimalHeader: true } }, { title: 'Hide Legend', props: { hideLegend: true } }, + { title: 'Hide header', props: { hideHeader: true } }, { title: 'Percentage Bars', props: { variant: 'percentages' as const } }, { title: 'Percentage Bars with suffix', diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx index 8f389778d0b..df9f6484ce3 100644 --- a/studio/components/ui/Charts/StackedBarChart.tsx +++ b/studio/components/ui/Charts/StackedBarChart.tsx @@ -15,6 +15,7 @@ interface Props extends CommonChartProps { xAxisFormatAsDate?: boolean displayDateInUtc?: boolean hideLegend?: boolean + hideHeader?: boolean } const StackedBarChart: React.FC = ({ size, @@ -31,6 +32,7 @@ const StackedBarChart: React.FC = ({ xAxisFormatAsDate = true, displayDateInUtc, hideLegend = false, + hideHeader = false, }) => { const { Container } = useChartSize(size) const { dataKeys, stackedData, percentagesStackedData } = useStacked({ @@ -43,13 +45,15 @@ const StackedBarChart: React.FC = ({ const [focusDataIndex, setFocusDataIndex] = useState(null) if (data.length === 0) return return ( -
    - +
    + {!hideHeader && ( + + )} { @@ -23,20 +24,20 @@ export const AuthReport: NextPageWithLayout = () => { const newUsers = hooks.newUsers() const cumulativeUsers = hooks.cumulativeUsers() const failedMigrations = hooks.failedMigrations() + const signUpProviders = hooks.signUpProviders() - const { isLoading, Layout } = usePresetReport([dailyActiveUsers, newUsers, cumulativeUsers]) + const { isLoading, Layout } = usePresetReport([ + dailyActiveUsers, + newUsers, + cumulativeUsers, + failedMigrations, + signUpProviders, + ]) return (
    - +

    User Activity

    + { data={dailyActiveUsers[0].logData!} renderer={renderDailyActiveUsers} /> + + +

    Sever Health

    +
    ) From 4ac96293a02b2a03d7f25b2034a4f75d3dafb8ef Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 16 Dec 2022 17:25:15 +0800 Subject: [PATCH 184/215] feat: add banned user widget and unverified users widget --- .../interfaces/Reports/Reports.constants.ts | 23 ++++++++++++++ .../interfaces/Reports/Statistic.tsx | 18 ++++++++++- .../Reports/renderers/AuthRenderers.tsx | 17 ++++++++++ .../components/ui/Charts/StackedBarChart.tsx | 2 +- studio/pages/project/[ref]/reports/auth.tsx | 31 +++++++++++++++++-- 5 files changed, 86 insertions(+), 5 deletions(-) diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index 187bb9a7543..e76f48949ac 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -162,6 +162,28 @@ export const PRESET_CONFIG: Record = { [Presets.AUTH]: { title: 'Auth', queries: { + bannedUsers: { + queryType: "db", + sql: ` +select + count(distinct u.id) as count +from auth.users u +where u.banned_until is not null and u.banned_until > now() +limit 1 + ` + }, + unverifiedUsers: { + queryType: "db", + sql: ` +select + date_trunc('day', u.created_at) as timestamp, + count(distinct u.id) as count +from auth.users u +where u.confirmed_at is null +group by timestamp +order by timestamp desc + ` + }, signUpProviders: { queryType: "db", sql: ` @@ -207,6 +229,7 @@ select date_trunc('day', u.created_at) as timestamp, sum(count(u.id)) over (order by date_trunc('day', u.created_at)) as count from auth.users as u +where u.confirmed_at is not null group by timestamp order by timestamp desc `, diff --git a/studio/components/interfaces/Reports/Statistic.tsx b/studio/components/interfaces/Reports/Statistic.tsx index 9f21c6feefc..61c26979c05 100644 --- a/studio/components/interfaces/Reports/Statistic.tsx +++ b/studio/components/interfaces/Reports/Statistic.tsx @@ -1,3 +1,4 @@ +import Sparkline from 'components/ui/Charts/Sparkline' import React from 'react' interface Props { @@ -6,12 +7,24 @@ interface Props { prefix?: string suffix?: string valueFormatter?: (value: Props['value']) => string + sparklineData?: Record[] + sparklineXAxis?: string + sparklineYAxis?: string } -const Statistic: React.FC = ({ prefix, suffix, value, startingValue }) => { +const Statistic: React.FC = ({ + prefix, + suffix, + value, + startingValue, + sparklineData, + sparklineXAxis, + sparklineYAxis, +}) => { const isNumber = typeof value === 'number' && !Number.isNaN(value) const percentageChange = startingValue && isNumber ? Number(startingValue) / Number(value) - 1 : null + const showSparkline = sparklineData && sparklineXAxis && sparklineYAxis return (
    @@ -29,6 +42,9 @@ const Statistic: React.FC = ({ prefix, suffix, value, startingValue }) => )}
    )} + {showSparkline && ( + + )}
    ) } diff --git a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx index f69dcc88e10..fbd38fb6c5f 100644 --- a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx @@ -4,6 +4,23 @@ import { DATETIME_FORMAT } from '../Reports.constants' import { ReportWidgetProps } from '../ReportWidget' import Statistic from '../Statistic' +export const renderBannedUsers = ( + props: ReportWidgetProps<{ + count: number + }> +) => { + const count = props.data.length === 0 ? 0 : props?.data?.[0]?.count + return +} +export const renderUnverifiedUsers = ( + props: ReportWidgetProps<{ + timestamp: string + count: number + }> +) => { + const count = props.data.length === 0 ? 0 : props?.data?.[0]?.count + return +} export const renderSignUpProviders = ( props: ReportWidgetProps<{ timestamp: string diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx index df9f6484ce3..d378cef19a8 100644 --- a/studio/components/ui/Charts/StackedBarChart.tsx +++ b/studio/components/ui/Charts/StackedBarChart.tsx @@ -43,7 +43,7 @@ const StackedBarChart: React.FC = ({ variant, }) const [focusDataIndex, setFocusDataIndex] = useState(null) - if (data.length === 0) return + if (!data || data.length === 0) return return (
    {!hideHeader && ( diff --git a/studio/pages/project/[ref]/reports/auth.tsx b/studio/pages/project/[ref]/reports/auth.tsx index 0d84ed91ebe..d57839ebf23 100644 --- a/studio/pages/project/[ref]/reports/auth.tsx +++ b/studio/pages/project/[ref]/reports/auth.tsx @@ -13,6 +13,8 @@ import { renderCumulativeUsers, renderFailedMigrations, renderSignUpProviders, + renderUnverifiedUsers, + renderBannedUsers, } from 'components/interfaces/Reports/renderers/AuthRenderers' export const AuthReport: NextPageWithLayout = () => { @@ -25,6 +27,8 @@ export const AuthReport: NextPageWithLayout = () => { const cumulativeUsers = hooks.cumulativeUsers() const failedMigrations = hooks.failedMigrations() const signUpProviders = hooks.signUpProviders() + const unverifiedUsers = hooks.unverifiedUsers() + const bannedUsers = hooks.bannedUsers() const { isLoading, Layout } = usePresetReport([ dailyActiveUsers, @@ -32,10 +36,11 @@ export const AuthReport: NextPageWithLayout = () => { cumulativeUsers, failedMigrations, signUpProviders, + unverifiedUsers, ]) return ( -
    +

    User Activity

    { -

    Sever Health

    + + + + +

    Sever Health

    Date: Fri, 16 Dec 2022 17:29:25 +0800 Subject: [PATCH 185/215] feat: add conditional rendering of sparkline --- studio/components/interfaces/Reports/Statistic.tsx | 4 ++-- .../interfaces/Reports/renderers/AuthRenderers.tsx | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/studio/components/interfaces/Reports/Statistic.tsx b/studio/components/interfaces/Reports/Statistic.tsx index 61c26979c05..e8f534ae003 100644 --- a/studio/components/interfaces/Reports/Statistic.tsx +++ b/studio/components/interfaces/Reports/Statistic.tsx @@ -7,7 +7,7 @@ interface Props { prefix?: string suffix?: string valueFormatter?: (value: Props['value']) => string - sparklineData?: Record[] + sparklineData?: any sparklineXAxis?: string sparklineYAxis?: string } @@ -43,7 +43,7 @@ const Statistic: React.FC = ({
    )} {showSparkline && ( - + )}
    ) diff --git a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx index fbd38fb6c5f..f61c26a5674 100644 --- a/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/AuthRenderers.tsx @@ -19,7 +19,15 @@ export const renderUnverifiedUsers = ( }> ) => { const count = props.data.length === 0 ? 0 : props?.data?.[0]?.count - return + + return ( + = 3 ? props.data : null} + sparklineXAxis="timestamp" + sparklineYAxis="count" + /> + ) } export const renderSignUpProviders = ( props: ReportWidgetProps<{ From fcbc5e88d12ed4922d1d16cc652d88d1319b1295 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 16 Dec 2022 17:35:56 +0800 Subject: [PATCH 186/215] feat: combine bot subreport into API report --- studio/tests/pages/projects/reports/product-reports.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/tests/pages/projects/reports/product-reports.test.js b/studio/tests/pages/projects/reports/product-reports.test.js index a30e33c1aea..885fcbe145a 100644 --- a/studio/tests/pages/projects/reports/product-reports.test.js +++ b/studio/tests/pages/projects/reports/product-reports.test.js @@ -7,7 +7,7 @@ import { get, post } from 'lib/common/fetch' import { useRouter } from 'next/router' import { render } from '../../../helpers' import { fireEvent, waitFor, screen } from '@testing-library/react' -import { ApiOverviewReport } from 'pages/project/[ref]/reports/api-overview' +import { ApiOverviewReport } from 'pages/project/[ref]/reports/api' import { ApiBotsReport } from 'pages/project/[ref]/reports/api-bots' import { AuthReport } from 'pages/project/[ref]/reports/auth' From f041bb3644a94898014198f48aa6beae6db81cd5 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 16 Dec 2022 17:37:31 +0800 Subject: [PATCH 187/215] feat: combine bot subreport into API report --- .../interfaces/Reports/Reports.constants.ts | 9 +- .../interfaces/Reports/Reports.types.ts | 4 +- .../Reports/renderers/ApiBotsRenderers.tsx | 94 ------------------- ...OverviewRenderers.tsx => ApiRenderers.tsx} | 92 ++++++++++++++++++ .../ReportsLayout/ReportsMenu.utils.ts | 12 +-- .../pages/project/[ref]/reports/api-bots.tsx | 51 ---------- .../reports/{api-overview.tsx => api.tsx} | 47 ++++++++-- .../projects/reports/product-reports.test.js | 6 +- 8 files changed, 139 insertions(+), 176 deletions(-) delete mode 100644 studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx rename studio/components/interfaces/Reports/renderers/{OverviewRenderers.tsx => ApiRenderers.tsx} (70%) delete mode 100644 studio/pages/project/[ref]/reports/api-bots.tsx rename studio/pages/project/[ref]/reports/{api-overview.tsx => api.tsx} (58%) diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index e76f48949ac..456c6c95a8d 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -34,8 +34,8 @@ export const DEFAULT_QUERY_PARAMS = { } export const PRESET_CONFIG: Record = { - [Presets.API_OVERVIEW]: { - title: '', + [Presets.API]: { + title: 'API', queries: { statusCodes: { queryType: 'logs', @@ -101,11 +101,6 @@ export const PRESET_CONFIG: Record = { LIMIT 50 `, }, - }, - }, - [Presets.API_BOTS]: { - title: 'API Bots', - queries: { userAgents: { queryType: 'logs', sql: ` diff --git a/studio/components/interfaces/Reports/Reports.types.ts b/studio/components/interfaces/Reports/Reports.types.ts index 419303e66af..b13d36dd3ba 100644 --- a/studio/components/interfaces/Reports/Reports.types.ts +++ b/studio/components/interfaces/Reports/Reports.types.ts @@ -1,10 +1,8 @@ import { ResponseError } from 'types' -import { LogsEndpointParams } from '../Settings/Logs' import { DEFAULT_QUERY_PARAMS } from './Reports.constants' export enum Presets { - API_OVERVIEW = 'overview', - API_BOTS = 'api_bots', + API = 'api', AUTH = 'auth', } diff --git a/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx b/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx deleted file mode 100644 index 9c92a616dad..00000000000 --- a/studio/components/interfaces/Reports/renderers/ApiBotsRenderers.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import Table from 'components/to-be-cleaned/Table' -import { ReportWidgetProps } from '../ReportWidget' - -export const renderUserAgents = ( - props: ReportWidgetProps<{ - user_agent: string - request_source: string - count: number - }> -) => { - return ( -
    - User Agents - Request Source - Count - - } - body={ - <> - {props.data.map((row, index) => { - return ( - - - {row.user_agent ? ( - row.user_agent - ) : ( - No user agent - )} - - {row.request_source} - - {row.count} - - - ) - })} - - } - /> - ) -} - -export const renderBotScores = ( - props: ReportWidgetProps<{ - ip: string - country: string - user_agent: string - path: string - bot_score: number - bot_verified: boolean - count: number - }> -) => { - return ( -
    - IP (Country) - Request Path - User Agent - Bot Score - Is Verified Bot - Count - - } - body={ - <> - {props.data.map((row, index) => { - return ( - - - {row.ip} ({row.country}) - - {row.path} - {row.user_agent} - {row.bot_score} - {row.bot_verified ? 'Yes' : 'No'} - - {row.count} - - - ) - })} - - } - /> - ) -} diff --git a/studio/components/interfaces/Reports/renderers/OverviewRenderers.tsx b/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx similarity index 70% rename from studio/components/interfaces/Reports/renderers/OverviewRenderers.tsx rename to studio/components/interfaces/Reports/renderers/ApiRenderers.tsx index 2fc2c8eac4e..e6e8156d296 100644 --- a/studio/components/interfaces/Reports/renderers/OverviewRenderers.tsx +++ b/studio/components/interfaces/Reports/renderers/ApiRenderers.tsx @@ -10,6 +10,98 @@ import { PathsDatum, StatusCodesDatum } from '../Reports.types' import { queryParamsToObject } from '../Reports.utils' import { ReportWidgetProps } from '../ReportWidget' +export const renderUserAgents = ( + props: ReportWidgetProps<{ + user_agent: string + request_source: string + count: number + }> +) => { + return ( +
    + User Agents + Request Source + Count + + } + body={ + <> + {props.data.map((row, index) => { + return ( + + + {row.user_agent ? ( + row.user_agent + ) : ( + No user agent + )} + + {row.request_source} + + {row.count} + + + ) + })} + + } + /> + ) +} + +export const renderBotScores = ( + props: ReportWidgetProps<{ + ip: string + country: string + user_agent: string + path: string + bot_score: number + bot_verified: boolean + count: number + }> +) => { + return ( +
    + IP (Country) + Request Path + User Agent + Bot Score + Is Verified Bot + Count + + } + body={ + <> + {props.data.map((row, index) => { + return ( + + + {row.ip} ({row.country}) + + {row.path} + {row.user_agent} + {row.bot_score} + {row.bot_verified ? 'Yes' : 'No'} + + {row.count} + + + ) + })} + + } + /> + ) +} + export const renderStatusCodesChart = (props: ReportWidgetProps) => ( { ...(productReports ? [ { - name: 'API Overview', - key: 'api-overview', - url: `/project/${ref}/reports/api-overview`, - items: [], - }, - { - name: 'API Bots', - key: 'api-bots', - url: `/project/${ref}/reports/api-bots`, + name: 'API', + key: 'api', + url: `/project/${ref}/reports/api`, items: [], }, { diff --git a/studio/pages/project/[ref]/reports/api-bots.tsx b/studio/pages/project/[ref]/reports/api-bots.tsx deleted file mode 100644 index 3df85b74abf..00000000000 --- a/studio/pages/project/[ref]/reports/api-bots.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { observer } from 'mobx-react-lite' -import { useRouter } from 'next/router' - -import { NextPageWithLayout } from 'types' -import { Presets } from 'components/interfaces/Reports/Reports.types' -import ReportsLayout from 'components/layouts/ReportsLayout/ReportsLayout' -import { hooksFactory, usePresetReport } from 'components/interfaces/Reports/Reports.utils' -import { PRESET_CONFIG } from 'components/interfaces/Reports/Reports.constants' -import { - renderBotScores, - renderUserAgents, -} from 'components/interfaces/Reports/renderers/ApiBotsRenderers' -import ReportWidget from 'components/interfaces/Reports/ReportWidget' - -export const ApiBotsReport: NextPageWithLayout = () => { - const router = useRouter() - const { ref } = router.query - const config = PRESET_CONFIG[Presets.API_BOTS] - const hooks = hooksFactory(ref as string, config) - const userAgents = hooks.userAgents() - const botScores = hooks.botScores() - const { isLoading, Layout } = usePresetReport([userAgents, botScores]) - return ( - -
    - - -
    -
    - ) -} - -ApiBotsReport.getLayout = (page) => {page} - -export default observer(ApiBotsReport) diff --git a/studio/pages/project/[ref]/reports/api-overview.tsx b/studio/pages/project/[ref]/reports/api.tsx similarity index 58% rename from studio/pages/project/[ref]/reports/api-overview.tsx rename to studio/pages/project/[ref]/reports/api.tsx index 2d266dabeaf..1c6257d180b 100644 --- a/studio/pages/project/[ref]/reports/api-overview.tsx +++ b/studio/pages/project/[ref]/reports/api.tsx @@ -11,17 +11,27 @@ import { renderErrorRateChart, renderRequestsPathsTable, renderStatusCodesChart, -} from 'components/interfaces/Reports/renderers/OverviewRenderers' + renderBotScores, + renderUserAgents +} from 'components/interfaces/Reports/renderers/ApiRenderers' -export const ApiOverviewReport: NextPageWithLayout = () => { +export const ApiReport: NextPageWithLayout = () => { const router = useRouter() const { ref } = router.query - const config = PRESET_CONFIG[Presets.API_OVERVIEW] + const config = PRESET_CONFIG[Presets.API] const hooks = hooksFactory(ref as string, config) const statusCodes = hooks.statusCodes() const errorRates = hooks.errorRates() const requestPaths = hooks.requestPaths() - const { isLoading, Layout } = usePresetReport([statusCodes, errorRates, requestPaths]) + const userAgents = hooks.userAgents() + const botScores = hooks.botScores() + const { isLoading, Layout } = usePresetReport([ + statusCodes, + errorRates, + requestPaths, + userAgents, + botScores, + ]) return (
    @@ -31,7 +41,7 @@ export const ApiOverviewReport: NextPageWithLayout = () => { className="col-span-4 col-start-1" title="API Status Codes" description="Distribution of API responses by status codes." - data={statusCodes[0].logData || []} + data={statusCodes[0].logData || []} renderer={renderStatusCodesChart} /> { className="col-span-4 col-start-1" title="Error Rate" description="Percentage of API 5XX and 4XX error responses." - data={errorRates[0].logData || []} + data={errorRates[0].logData || []} renderer={renderErrorRateChart} /> { data={requestPaths[0].logData || []} renderer={renderRequestsPathsTable} /> + +

    Bots

    + + +
    ) } -ApiOverviewReport.getLayout = (page) => {page} +ApiReport.getLayout = (page) => {page} -export default observer(ApiOverviewReport) +export default observer(ApiReport) diff --git a/studio/tests/pages/projects/reports/product-reports.test.js b/studio/tests/pages/projects/reports/product-reports.test.js index 885fcbe145a..a82267a2717 100644 --- a/studio/tests/pages/projects/reports/product-reports.test.js +++ b/studio/tests/pages/projects/reports/product-reports.test.js @@ -7,8 +7,7 @@ import { get, post } from 'lib/common/fetch' import { useRouter } from 'next/router' import { render } from '../../../helpers' import { fireEvent, waitFor, screen } from '@testing-library/react' -import { ApiOverviewReport } from 'pages/project/[ref]/reports/api' -import { ApiBotsReport } from 'pages/project/[ref]/reports/api-bots' +import { ApiReport } from 'pages/project/[ref]/reports/api' import { AuthReport } from 'pages/project/[ref]/reports/auth' beforeEach(() => { @@ -25,8 +24,7 @@ beforeEach(() => { }) describe.each([ - { Page: ApiOverviewReport, contains: ['API Overview'] }, - { Page: ApiBotsReport, contains: ['API Bots'] }, + { Page: ApiReport, contains: ['API'] }, { Page: AuthReport, contains: ['Auth'] }, ])('$Page rendering', ({ Page, contains }) => { test(`contains ${contains}`, async () => { From a66766ece2b204768fd0b4450f20d854d6739d37 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 16 Dec 2022 17:59:32 +0800 Subject: [PATCH 188/215] fix: add db query timestamp filtering --- .../interfaces/Reports/Reports.constants.ts | 17 +++++++------- .../interfaces/Reports/Reports.types.ts | 23 +++++++++---------- .../interfaces/Reports/Statistic.tsx | 7 +++++- studio/components/ui/Charts/Sparkline.tsx | 10 +++----- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index 456c6c95a8d..90010822e2e 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -159,7 +159,7 @@ export const PRESET_CONFIG: Record = { queries: { bannedUsers: { queryType: "db", - sql: ` + sql: (_params)=> ` select count(distinct u.id) as count from auth.users u @@ -169,25 +169,26 @@ limit 1 }, unverifiedUsers: { queryType: "db", - sql: ` + sql: (params)=> ` select date_trunc('day', u.created_at) as timestamp, count(distinct u.id) as count from auth.users u -where u.confirmed_at is null +where u.confirmed_at is null and '${params.iso_timestamp_start}' < u.created_at group by timestamp order by timestamp desc ` }, signUpProviders: { queryType: "db", - sql: ` + sql: (params) => ` select sum(count(u.id)) over (order by date_trunc('day', u.created_at)) as count, date_trunc('day', u.created_at) as timestamp, i.provider as provider from auth.users u join auth.identities as i on u.id = i.user_id +where date_trunc('day', u.created_at) > '${params.iso_timestamp_start}' group by timestamp, provider order by timestamp desc ` @@ -218,25 +219,25 @@ order by }, cumulativeUsers: { queryType: 'db', - sql: ` + sql: params => ` select date_trunc('day', u.created_at) as timestamp, sum(count(u.id)) over (order by date_trunc('day', u.created_at)) as count from auth.users as u -where u.confirmed_at is not null +where u.confirmed_at is not null and date_trunc('day', u.created_at) > '${params.iso_timestamp_start}' group by timestamp order by timestamp desc `, }, newUsers: { queryType: 'db', - sql: ` + sql: params => ` select date_trunc('day', u.created_at) as timestamp, count(u.id) as count from auth.users as u -where u.confirmed_at is not null +where u.confirmed_at is not null and date_trunc('day', u.created_at) > '${params.iso_timestamp_start}' group by timestamp order by diff --git a/studio/components/interfaces/Reports/Reports.types.ts b/studio/components/interfaces/Reports/Reports.types.ts index b13d36dd3ba..20c07dd463b 100644 --- a/studio/components/interfaces/Reports/Reports.types.ts +++ b/studio/components/interfaces/Reports/Reports.types.ts @@ -23,20 +23,19 @@ export interface DbQueryHandler { export type MetaQueryResponse = any & { error: ResponseError } export type BaseReportParams = typeof DEFAULT_QUERY_PARAMS & { sql?: string } & unknown - -export type VariableSql = string | ((params: BaseReportParams) => string) - -export type PresetSql = string | VariableSql - export interface PresetConfig { title: string - queries: Record< - string, - { - sql: PresetSql - queryType: 'logs' | 'db' - } - > + queries: Record +} + +interface DbQuery { + queryType: 'db' + sql: (params: BaseReportParams) => string +} + +interface LogsQuery { + queryType: 'logs' + sql: string } export interface StatusCodesDatum { timestamp: number diff --git a/studio/components/interfaces/Reports/Statistic.tsx b/studio/components/interfaces/Reports/Statistic.tsx index e8f534ae003..3cc6cd8bdac 100644 --- a/studio/components/interfaces/Reports/Statistic.tsx +++ b/studio/components/interfaces/Reports/Statistic.tsx @@ -43,7 +43,12 @@ const Statistic: React.FC = ({ )} {showSparkline && ( - + )} ) diff --git a/studio/components/ui/Charts/Sparkline.tsx b/studio/components/ui/Charts/Sparkline.tsx index 855a9f86383..c6c2bf1079b 100644 --- a/studio/components/ui/Charts/Sparkline.tsx +++ b/studio/components/ui/Charts/Sparkline.tsx @@ -1,11 +1,7 @@ -import { useState } from 'react' -import { LineChart, Bar, XAxis, Tooltip, Legend, Cell, TooltipProps, Line } from 'recharts' -import ChartHeader from './ChartHeader' -import { CHART_COLORS, STACK_COLORS, DateTimeFormats } from './Charts.constants' +import { LineChart, XAxis, Line } from 'recharts' +import { CHART_COLORS } from './Charts.constants' import { CommonChartProps } from './Charts.types' -import { timestampFormatter, useChartSize, useStacked } from './Charts.utils' -import { precisionFormatter } from './Charts.utils' -import NoDataPlaceholder from './NoDataPlaceholder' +import { useChartSize } from './Charts.utils' interface Props extends CommonChartProps { xAxisKey: string yAxisKey: string From 179402ef927dedc68f01a2fb3cb6d24d84327a19 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Fri, 16 Dec 2022 18:01:54 +0800 Subject: [PATCH 189/215] fix: remove unused file --- studio/components/ui/ProductMenu/ProductMenuGroup.tsx | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 studio/components/ui/ProductMenu/ProductMenuGroup.tsx diff --git a/studio/components/ui/ProductMenu/ProductMenuGroup.tsx b/studio/components/ui/ProductMenu/ProductMenuGroup.tsx deleted file mode 100644 index 96f4ea20ef0..00000000000 --- a/studio/components/ui/ProductMenu/ProductMenuGroup.tsx +++ /dev/null @@ -1,3 +0,0 @@ -const ProductMenuGroup = ()=>{ - -} \ No newline at end of file From d52154f68654673dfbf0c877c64ef438ea65fc81 Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Wed, 21 Dec 2022 21:19:41 +0800 Subject: [PATCH 190/215] chore: fix failing typecheck --- studio/components/interfaces/Reports/Reports.types.ts | 6 +++--- studio/hooks/analytics/useDbQuery.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/studio/components/interfaces/Reports/Reports.types.ts b/studio/components/interfaces/Reports/Reports.types.ts index 20c07dd463b..0cc81dd470e 100644 --- a/studio/components/interfaces/Reports/Reports.types.ts +++ b/studio/components/interfaces/Reports/Reports.types.ts @@ -28,12 +28,12 @@ export interface PresetConfig { queries: Record } -interface DbQuery { +export interface DbQuery { queryType: 'db' - sql: (params: BaseReportParams) => string + sql: string | ((params: BaseReportParams) => string) } -interface LogsQuery { +export interface LogsQuery { queryType: 'logs' sql: string } diff --git a/studio/hooks/analytics/useDbQuery.tsx b/studio/hooks/analytics/useDbQuery.tsx index 45d9236d4f9..9c229fb7f83 100644 --- a/studio/hooks/analytics/useDbQuery.tsx +++ b/studio/hooks/analytics/useDbQuery.tsx @@ -4,12 +4,12 @@ import { DbQueryData, DbQueryHandler, MetaQueryResponse, - PresetSql, + DbQuery, } from 'components/interfaces/Reports/Reports.types' import { useStore } from 'hooks' import useSWR from 'swr' -type UseDbQuery = (sql: PresetSql, params?: BaseReportParams) => [DbQueryData, DbQueryHandler] +type UseDbQuery = (sql: DbQuery['sql'], params?: BaseReportParams) => [DbQueryData, DbQueryHandler] const useDbQuery: UseDbQuery = (sql, params = DEFAULT_QUERY_PARAMS) => { const { meta } = useStore() From f674a35d3df5f494ca1fdccce4538c5a726cad3a Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Wed, 21 Dec 2022 23:04:21 +0800 Subject: [PATCH 191/215] fix: ui fixes for datepicker --- .../components/interfaces/Reports/Reports.constants.ts | 7 +++---- studio/components/interfaces/Reports/Reports.utils.tsx | 10 ++++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts index 90010822e2e..fedef4cdd7e 100644 --- a/studio/components/interfaces/Reports/Reports.constants.ts +++ b/studio/components/interfaces/Reports/Reports.constants.ts @@ -6,13 +6,12 @@ export const LAYOUT_COLUMN_COUNT = 24 export const REPORTS_DATEPICKER_HELPERS: DatetimeHelper[] = [ { - text: 'Last 7 days', - calcFrom: () => dayjs().subtract(7, 'day').startOf('day').toISOString(), + text: 'Last day', calcTo: () => '', - default: true, + calcFrom: () => dayjs().subtract(1, 'day').startOf('day').toISOString(), }, { - text: 'Last 14 days', + text: 'Last 7 days', calcFrom: () => dayjs().subtract(7, 'day').startOf('day').toISOString(), calcTo: () => '', }, diff --git a/studio/components/interfaces/Reports/Reports.utils.tsx b/studio/components/interfaces/Reports/Reports.utils.tsx index 4d07a2686ee..41e0ee4d975 100644 --- a/studio/components/interfaces/Reports/Reports.utils.tsx +++ b/studio/components/interfaces/Reports/Reports.utils.tsx @@ -63,6 +63,12 @@ export const usePresetReport = (hooks: PresetHookResult[]) => { }) } const isLoading = data.map((datum) => datum.isLoading).some((v) => v) + const helpers = REPORTS_DATEPICKER_HELPERS.map((helper, index) => { + if (tier?.key === 'FREE' && index === 0) return { ...helper, default: true } + if (tier?.key === 'PRO' && index === 1) return { ...helper, default: true } + return { ...helper, default: false } + }) + const Layout: React.FC<{ title: string }> = ({ title, children }) => (

    {title}

    @@ -70,8 +76,8 @@ export const usePresetReport = (hooks: PresetHookResult[]) => {
    {day2.steps[0].title}
    - + New
    @@ -454,7 +454,7 @@ export default function launchweek() {
    {day3.steps[0].title} - + Updated
    @@ -516,7 +516,7 @@ export default function launchweek() {
    {day4.steps[0].title} - + New
    @@ -585,7 +585,7 @@ export default function launchweek() { {day5.steps[0].title} - + New @@ -778,7 +778,7 @@ export default function launchweek() { />
    - + Updated @@ -823,7 +823,7 @@ export default function launchweek() { />
    - + New @@ -868,7 +868,7 @@ export default function launchweek() { />
    - + New @@ -913,7 +913,7 @@ export default function launchweek() { />
    - + Experimental @@ -958,7 +958,7 @@ export default function launchweek() { />
    - + Upgrade @@ -1003,7 +1003,7 @@ export default function launchweek() { />
    - + Upgrade @@ -1029,7 +1029,9 @@ export default function launchweek() {
    - + Submissions Closed

    Launch Week Hackathon

    From f3c3d566fc774ce33d9f1ceb1cc2446a32bfab43 Mon Sep 17 00:00:00 2001 From: Francisco Mazzoni Date: Thu, 22 Dec 2022 13:20:51 -0300 Subject: [PATCH 194/215] small badge fix --- apps/www/pages/launch-week/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/www/pages/launch-week/index.tsx b/apps/www/pages/launch-week/index.tsx index c6e3b798330..10cddcf4113 100644 --- a/apps/www/pages/launch-week/index.tsx +++ b/apps/www/pages/launch-week/index.tsx @@ -102,7 +102,7 @@ export default function launchweek() { @@ -1030,7 +1030,7 @@ export default function launchweek() {
    Submissions Closed @@ -1217,7 +1217,7 @@ export default function launchweek() { From 2b27b688e6ca0a2d4028eb602f8da646f4973baa Mon Sep 17 00:00:00 2001 From: Chensokheng <52232579+Chensokheng@users.noreply.github.com> Date: Fri, 23 Dec 2022 00:01:11 +0700 Subject: [PATCH 195/215] fix(docs): change navbar color to dark on dark mode --- apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx | 1 + apps/docs/layouts/SiteLayout.tsx | 2 ++ 2 files changed, 3 insertions(+) diff --git a/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx b/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx index d5a706d6cb2..b1d344b081d 100644 --- a/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx @@ -42,6 +42,7 @@ const TopNavBarRef: FC = () => { const key = localStorage.getItem('supabaseDarkMode') document.documentElement.className = key === 'true' ? 'dark' : '' + document.documentElement.style.colorScheme = key === 'true' ? 'dark' : '' } const onSelectVersion = (version: string) => { diff --git a/apps/docs/layouts/SiteLayout.tsx b/apps/docs/layouts/SiteLayout.tsx index 9aa8c37d9c0..eb017378efd 100644 --- a/apps/docs/layouts/SiteLayout.tsx +++ b/apps/docs/layouts/SiteLayout.tsx @@ -294,8 +294,10 @@ const SiteLayout = ({ children }) => { 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' : '' } }, []) From b2d8e8eb4eebc6878349e9ba2d23e7fe4a49f0ac Mon Sep 17 00:00:00 2001 From: dannykng Date: Thu, 22 Dec 2022 10:27:34 -0800 Subject: [PATCH 196/215] Update more links --- .../resources/supabase-cli/local-development.mdx | 2 +- apps/www/_blog/2021-11-29-community-day-lw3.mdx | 2 +- .../2022-04-20-partner-gallery-works-with-supabase.mdx | 2 +- ...ck-consolidate-slackbot-to-consolidate-messages.mdx | 10 +++------- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/apps/docs/pages/guides/resources/supabase-cli/local-development.mdx b/apps/docs/pages/guides/resources/supabase-cli/local-development.mdx index 3ff1cb1c82a..90490ae4e55 100644 --- a/apps/docs/pages/guides/resources/supabase-cli/local-development.mdx +++ b/apps/docs/pages/guides/resources/supabase-cli/local-development.mdx @@ -54,7 +54,7 @@ git init supabase init ``` -Make sure Docker is running. The [start](/docs/reference/cli/usage#supabase-start) command uses Docker to start the Supabase [services](/docs/architecture). +Make sure Docker is running. The [start](/docs/reference/cli/usage#supabase-start) command uses Docker to start the Supabase [services](/docs/guides/getting-started/architecture). This command may take a while to run if this is the first time using the CLI. ```bash diff --git a/apps/www/_blog/2021-11-29-community-day-lw3.mdx b/apps/www/_blog/2021-11-29-community-day-lw3.mdx index 4ac1ea6ea05..801bfc24217 100644 --- a/apps/www/_blog/2021-11-29-community-day-lw3.mdx +++ b/apps/www/_blog/2021-11-29-community-day-lw3.mdx @@ -22,7 +22,7 @@ So let's get cracking, we've got a lot to cover. 🚀 ## Open Source Spotlight: Kong API Gateway -Supabase provides a whole bunch of features that are made up of a [collection of open-source tools](/docs/architecture). To orchestrate these different services and functionalities we use the [Kong API Gateway](https://github.com/Kong/kong). +Supabase provides a whole bunch of features that are made up of a [collection of open-source tools](/docs/guides/getting-started/architecture). To orchestrate these different services and functionalities we use the [Kong API Gateway](https://github.com/Kong/kong). As a user of our hosted offering you don't really need to know all this, but we love showcasing the amazing open-source tools that we work with, and so we've invited [Vik Gamov](https://twitter.com/gAmUssA) to give you a little intro to [Kong](https://github.com/Kong/kong): diff --git a/apps/www/_blog/2022-04-20-partner-gallery-works-with-supabase.mdx b/apps/www/_blog/2022-04-20-partner-gallery-works-with-supabase.mdx index eaea01bbd94..aaf5e4d21b0 100644 --- a/apps/www/_blog/2022-04-20-partner-gallery-works-with-supabase.mdx +++ b/apps/www/_blog/2022-04-20-partner-gallery-works-with-supabase.mdx @@ -19,7 +19,7 @@ To keep track of all the great tools and people that “Work With Supabase”, w ![partner-gallery-screenshot.png](/images/blog/partner-gallery/partner-gallery-screenshot.png) -As with anything at Supabase, the Partner Gallery is built on top of the [Supabase stack](/docs/architecture) and is, of course, open-source. If you’re looking to build a partner gallery yourself, feel free to use our template as a start: [github.com/supabase-community/partner-gallery-example](https://github.com/supabase-community/partner-gallery-example). It’s packed with goodies like [Postgres Full Text Search](/docs/guides/database/full-text-search), `next-image` with Supabase Storage, and sending emails based on an insert trigger with Supabase Edge Functions. +As with anything at Supabase, the Partner Gallery is built on top of the [Supabase stack](/docs/guides/getting-started/architecture) and is, of course, open-source. If you’re looking to build a partner gallery yourself, feel free to use our template as a start: [github.com/supabase-community/partner-gallery-example](https://github.com/supabase-community/partner-gallery-example). It’s packed with goodies like [Postgres Full Text Search](/docs/guides/database/full-text-search), `next-image` with Supabase Storage, and sending emails based on an insert trigger with Supabase Edge Functions. # Partner showcase - new integrations and expert services diff --git a/apps/www/_blog/2022-08-09-slack-consolidate-slackbot-to-consolidate-messages.mdx b/apps/www/_blog/2022-08-09-slack-consolidate-slackbot-to-consolidate-messages.mdx index 2172b0330a7..7581b57e33a 100644 --- a/apps/www/_blog/2022-08-09-slack-consolidate-slackbot-to-consolidate-messages.mdx +++ b/apps/www/_blog/2022-08-09-slack-consolidate-slackbot-to-consolidate-messages.mdx @@ -13,7 +13,7 @@ date: '2022-08-09' toc_depth: 3 --- -Supabase is a platform-as-a-service built on top of PostgreSQL and [many other amazing open-source tools](https://supabase.com/docs/architecture). It’s a fantastic option to create data-intensive apps and tooling. Taking advantage of the community-made [Python SDK](https://github.com/supabase-community/supabase-py) and [Slack's Python SDK](https://slack.dev/python-slack-sdk/), you can automate tasks and build apps for several use cases. +Supabase is a platform-as-a-service built on top of PostgreSQL and [many other amazing open-source tools](https://supabase.com/docs/guides/getting-started/architecture). It’s a fantastic option to create data-intensive apps and tooling. Taking advantage of the community-made [Python SDK](https://github.com/supabase-community/supabase-py) and [Slack's Python SDK](https://slack.dev/python-slack-sdk/), you can automate tasks and build apps for several use cases. ## Prerequisites @@ -161,9 +161,9 @@ Slack links have the following format: https://ORGANIZATION.slack.com/archives/ channel_id/pmessage_id -
    +
    Organization: subdomain used in Slack -
    +
    Channel ID: It is the string that you'll need to enter in slack channels as the channel id e.g C0000ABC02DE @@ -418,7 +418,3 @@ If you have any questions please reach out via [Twitter](https://twitter.com/sup - [Python data loading with Supabase](https://supabase.com/blog/loading-data-supabase-python) - [Visualizing Supabase Data using Metabase](https://supabase.com/blog/visualizing-supabase-data-using-metabase) - [Supabase-py (Database) on Replit](https://replit.com/@Supabase/Supabase-py-Database?v=1) - - - - From dc16f8ac8ef67180c42c8f556c7e775bcbee6ae4 Mon Sep 17 00:00:00 2001 From: dannykng Date: Thu, 22 Dec 2022 16:14:22 -0800 Subject: [PATCH 197/215] Add redirect for Zoom auth guide --- apps/www/lib/redirects.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js index ff18f604298..cd0b30a8080 100644 --- a/apps/www/lib/redirects.js +++ b/apps/www/lib/redirects.js @@ -1707,6 +1707,11 @@ module.exports = [ source: '/docs/guides/auth/auth-workos', destination: '/docs/guides/auth/social-login/auth-workos', }, + { + permanent: true, + source: '/docs/guides/auth/auth-zoom', + destination: '/docs/guides/auth/social-login/auth-zoom', + }, { permanent: true, source: '/docs/guides/database', From cdde6b1bc3f261e5ab6d12ec23f77eb8fdcbeb9d Mon Sep 17 00:00:00 2001 From: dannykng Date: Thu, 22 Dec 2022 16:16:23 -0800 Subject: [PATCH 198/215] Fix broken link to estuary.dev --- .../pages/guides/integrations/estuary.mdx | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/apps/docs/pages/guides/integrations/estuary.mdx b/apps/docs/pages/guides/integrations/estuary.mdx index 549f6cbc549..e8287eaff62 100644 --- a/apps/docs/pages/guides/integrations/estuary.mdx +++ b/apps/docs/pages/guides/integrations/estuary.mdx @@ -3,62 +3,64 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'estuary', title: 'Estuary', - description: 'Create a real-time data pipeline connecting Firestore and Supabase to make migration simple.', + description: + 'Create a real-time data pipeline connecting Firestore and Supabase to make migration simple.', } Estuary Flow is a platform for creating [real-time data pipelines at scale](https://www.estuary.dev/). -It combines the intuitive interface of an ELT service with an event-driven runtime and a variety of open-source connectors. +It combines the intuitive interface of an ELT service with an event-driven runtime and a variety of open-source connectors. -You can use Flow to migrate your data from Firestore to the Postgres database in your Supabase project. +You can use Flow to migrate your data from Firestore to the Postgres database in your Supabase project. You do this by building a real-time pipeline that captures data from Firestore and materializes (loads) that data to Postgres. -Once created, the pipeline backfills all your historical data from Firestore and continues to process new data events in real time. +Once created, the pipeline backfills all your historical data from Firestore and continues to process new data events in real time. ## Prerequisites Before you begin, you'll need: -* An [Estuary account](go.estuary.dev/sign-up). +- An [Estuary account](https://go.estuary.dev/sign-up). -* For your Firestore database: - * A Google service account with read access to your Firestore database, via [roles/datastore.viewer](https://cloud.google.com/datastore/docs/access/iam). You can assign this role when you [create the service account](https://cloud.google.com/iam/docs/creating-managing-service-accounts#creating), or [add it to an existing service account](https://cloud.google.com/iam/docs/granting-changing-revoking-access#single-role). - * A generated [JSON service account key](https://cloud.google.com/iam/docs/creating-managing-service-account-keys#creating) for the service account. +- For your Firestore database: -* A Supabase project + - A Google service account with read access to your Firestore database, via [roles/datastore.viewer](https://cloud.google.com/datastore/docs/access/iam). You can assign this role when you [create the service account](https://cloud.google.com/iam/docs/creating-managing-service-accounts#creating), or [add it to an existing service account](https://cloud.google.com/iam/docs/granting-changing-revoking-access#single-role). + - A generated [JSON service account key](https://cloud.google.com/iam/docs/creating-managing-service-account-keys#creating) for the service account. + +- A Supabase project ## Step 1: Capture your Firestore data -You'll start by creating a **capture**, a task in Flow that connects to your data source system: in this case, Firestore. This process will create one or more data **collections**, backed by a real-time data lake. +You'll start by creating a **capture**, a task in Flow that connects to your data source system: in this case, Firestore. This process will create one or more data **collections**, backed by a real-time data lake. 1. Go to the Flow web application at [dashboard.estuary.dev](http://dashboard.estuary.dev) and sign in using the credentials provided by your Estuary account manager. 2. Click the **Captures** tab and choose **New Capture**. -3. Locate and select the **Google Firestore** card. +3. Locate and select the **Google Firestore** card. A form appears with the properties required for a Firestore capture. -4. Set a name for your capture. - +4. Set a name for your capture. + Click inside the **Name** field to generate a drop-down menu of available **prefixes** and select one (likely, this will be the name of your organization). Append a unique capture name after the `/` to create the full name, for example, `acmeCo/myFirestoreCapture`. 5. Fill out the required properties for Firestore. - * **Database**: Flow can autodetect the database name, but you may optionally specify it here. This is helpful if the service account used has access to multiple Firebase projects. Your database name usually follows the format `projects/$PROJECTID/databases/(default)`. - * **Credentials**: The JSON service account key created per the prerequisites. + - **Database**: Flow can autodetect the database name, but you may optionally specify it here. This is helpful if the service account used has access to multiple Firebase projects. Your database name usually follows the format `projects/$PROJECTID/databases/(default)`. + - **Credentials**: The JSON service account key created per the prerequisites. 6. Click **Discover Endpoint**. - Flow uses the provided configuration to initiate a connection with Firestore. It generates a capture specification and details of the collections that it will create, once published. +Flow uses the provided configuration to initiate a connection with Firestore. It generates a capture specification and details of the collections that it will create, once published. -7. Use the **Specification Editor** to view the [JSON schemas](https://docs.estuary.dev/concepts/schemas/) for each collection and make sure they are formatted correctly for your needs. If they're not, you can edit them. +7. Use the **Specification Editor** to view the [JSON schemas](https://docs.estuary.dev/concepts/schemas/) for each collection and make sure they are formatted correctly for your needs. If they're not, you can edit them. 8. Click **Save and publish**. - You'll see a notification when the capture publishes successfully. +You'll see a notification when the capture publishes successfully. - The data currently in your Firestore database has been captured, and future updates to it will be captured continuously. +The data currently in your Firestore database has been captured, and future updates to it will be captured continuously. - Click **Materialize Collections** to continue. +Click **Materialize Collections** to continue. ## Step 2: Materialize your collections to Postgres @@ -66,35 +68,35 @@ Next, you'll add a Postgres materialization to connect the captured collections 1. On the **Create Materialization** page, search for and select the **PostgreSQL** tile. - A form appears with the properties required for a Postgres materialization. +A form appears with the properties required for a Postgres materialization. 2. Choose a unique name for your materialization like you did when naming your capture; for example, `acmeCo/mySupabaseMaterialization`. 3. Fill out the required properties for PostgreSQL. You can find most of these in Supabase by going to the **Settings** section and clicking **Database**. - * **Address**: Format at `:`. - * **User**: Usually, this is `postgres`. - * **Password**: The password you set when you created your Supabase project. + - **Address**: Format at `:`. + - **User**: Usually, this is `postgres`. + - **Password**: The password you set when you created your Supabase project. 4. Scroll down to view the **Collection Selector** and fill in the **Table** field for each collection. - The collections you just created have already been selected, but you must provide names for the tables to which they'll be materialized. +The collections you just created have already been selected, but you must provide names for the tables to which they'll be materialized. -5. Click **Discover Endpoint**. - - Flow uses the provided configuration to initiate a connection to your Supabase Postgres database and generate a specification. +5. Click **Discover Endpoint**. -6. Click **Save and Publish**. You'll see a notification when the full materialization publishes successfully. +Flow uses the provided configuration to initiate a connection to your Supabase Postgres database and generate a specification. -Your Firestore collections are copied to tables in Supabase. As long as you leave the capture and materialation running, any changes to the Firestore data will be reflected in Supabase in milliseconds. +6. Click **Save and Publish**. You'll see a notification when the full materialization publishes successfully. + +Your Firestore collections are copied to tables in Supabase. As long as you leave the capture and materialation running, any changes to the Firestore data will be reflected in Supabase in milliseconds. ## Resources For more information, visit the [Flow docs](https://docs.estuary.dev/). In particular: - * [Guide to create a Data Flow](https://docs.estuary.dev/guides/create-dataflow/) - * [Firestore capture connector](https://docs.estuary.dev/reference/Connectors/capture-connectors/google-firestore/) - * [Postgres materializaiton connector](https://docs.estuary.dev/reference/Connectors/materialization-connectors/PostgreSQL/) +- [Guide to create a Data Flow](https://docs.estuary.dev/guides/create-dataflow/) +- [Firestore capture connector](https://docs.estuary.dev/reference/Connectors/capture-connectors/google-firestore/) +- [Postgres materializaiton connector](https://docs.estuary.dev/reference/Connectors/materialization-connectors/PostgreSQL/) export const Page = ({ children }) => From d22b938755d59382485462708a1d5e6815fe450c Mon Sep 17 00:00:00 2001 From: dannykng Date: Thu, 22 Dec 2022 16:19:10 -0800 Subject: [PATCH 199/215] Fix link to access control doc --- apps/docs/pages/guides/platform/network-restrictions.mdx | 2 +- apps/www/lib/redirects.js | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/docs/pages/guides/platform/network-restrictions.mdx b/apps/docs/pages/guides/platform/network-restrictions.mdx index c2286c6a4f5..ce2f320a023 100644 --- a/apps/docs/pages/guides/platform/network-restrictions.mdx +++ b/apps/docs/pages/guides/platform/network-restrictions.mdx @@ -20,7 +20,7 @@ To get started: 1. [Install](/docs/guides/cli) the Supabase CLI 1.22.0+. 1. [Log in](/docs/guides/cli/local-development#log-in-to-the-supabase-cli) to your Supabase account using the CLI. -1. Ensure that you have [Owner or Admin permissions](/docs/guides/hosting/platform/access-control#manage-team-members) for the project that you are enabling network restrictions. +1. Ensure that you have [Owner or Admin permissions](/docs/guides/platform/access-control#manage-team-members) for the project that you are enabling network restrictions. ## Check restrictions diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js index cd0b30a8080..56951babe70 100644 --- a/apps/www/lib/redirects.js +++ b/apps/www/lib/redirects.js @@ -535,12 +535,16 @@ module.exports = [ source: '/docs/reference/postgres/publications', destination: '/docs/guides/database/replication', }, - { permanent: true, source: '/docs/guides/hosting/platform', destination: '/docs/guides/platform', }, + { + permanent: true, + source: '/docs/guides/hosting/platform/access-control', + destination: '/docs/guides/platform/access-control', + }, { permanent: true, source: '/docs/guides/self-hosting/overview', From 60cf5c30ff6075924de9090e205f8db9b1fc1915 Mon Sep 17 00:00:00 2001 From: dannykng Date: Thu, 22 Dec 2022 16:22:47 -0800 Subject: [PATCH 200/215] Fix Docker link --- apps/docs/pages/guides/self-hosting.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/self-hosting.mdx b/apps/docs/pages/guides/self-hosting.mdx index 3092dd9cf45..cdd7b69cc57 100644 --- a/apps/docs/pages/guides/self-hosting.mdx +++ b/apps/docs/pages/guides/self-hosting.mdx @@ -72,7 +72,7 @@ For working with JWT and Auth functions. Supabase creates several default roles in your Postgres database. To restore defaults at any time you can run the commands inside the [schema initialization scripts](https://github.com/supabase/postgres/tree/develop/migrations/db/init-scripts). Remember to change your -[role passwords](./docker#securing-your-setup) before deploying to production environments. +[role passwords](/docs/guides/self-hosting/docker#securing-your-setup) before deploying to production environments. ##### `postgres` From 20285a34d9232c17a5dba8fe6ac08b328c77295c Mon Sep 17 00:00:00 2001 From: dannykng Date: Thu, 22 Dec 2022 16:29:57 -0800 Subject: [PATCH 201/215] Fix link to Chinese translated README --- apps/www/_blog/2021-05-03-supabase-beta-april-2021.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/_blog/2021-05-03-supabase-beta-april-2021.mdx b/apps/www/_blog/2021-05-03-supabase-beta-april-2021.mdx index 7575e01d9b6..e7d3215ca92 100644 --- a/apps/www/_blog/2021-05-03-supabase-beta-april-2021.mdx +++ b/apps/www/_blog/2021-05-03-supabase-beta-april-2021.mdx @@ -49,7 +49,7 @@ So today we're shipping Light Mode. Access it in the settings of your [Dashboard With the help of the community, we [started internationalizing](https://github.com/supabase/supabase/issues/1341) our main repository: - [Arabic | العربية](https://github.com/supabase/supabase/blob/master/i18n/README.ar.md) -- [Chinese / 中文](https://github.com/supabase/supabase/blob/master/i18n/README.cn.md) +- [Chinese / 中文](https://github.com/supabase/supabase/blob/master/i18n/README.zh-cn.md) - [Dutch / Nederlands](https://github.com/supabase/supabase/blob/master/i18n/README.nl.md) - [English](https://github.com/supabase/supabase) - [French / Français](https://github.com/supabase/supabase/blob/master/i18n/README.fr.md) From fa6ed5fd71ef400d0607f2ad21cdcc750257afb2 Mon Sep 17 00:00:00 2001 From: dannykng Date: Thu, 22 Dec 2022 16:42:28 -0800 Subject: [PATCH 202/215] Fix links to weweb docs --- apps/docs/pages/guides/integrations/weweb.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/pages/guides/integrations/weweb.mdx b/apps/docs/pages/guides/integrations/weweb.mdx index c65a695400e..85809a457a9 100644 --- a/apps/docs/pages/guides/integrations/weweb.mdx +++ b/apps/docs/pages/guides/integrations/weweb.mdx @@ -103,7 +103,7 @@ If we only ask for the data from the `location` field of the `vehicles` table, S Assuming you were able to fetch data from Supabase in a WeWeb Collection, you'll be able to bind the data from that Collection on your WeWeb pages. -In the example below, we chose to display the car model and mileage in the [Data Grid element](https://docs.weweb.io/data-grid/) that comes out-of-the-box in WeWeb: +In the example below, we chose to display the car model and mileage in the [Data Grid element](https://docs.weweb.io/add-elements/elements/data-grid.html) that comes out-of-the-box in WeWeb: ![](https://weweb-changelog.ghost.io/content/images/2022/10/CleanShot-2022-10-21-at-12.31.05@2x.png) @@ -111,7 +111,7 @@ We chose this element because it includes a built-in inline editing mode we'll w #### 🔥 Pro Tip 🔥 -> In WeWeb, you can [bind arrays of data to any Container](https://docs.weweb.io/display-collection-list/). Just bear in mind that the first child of the Container you bind the Collection to will be the repeated item. With that in mind, you might want the first child Element to be another Container with a number of items inside like a title, description, button or image. +> In WeWeb, you can [bind arrays of data to any Container](https://docs.weweb.io/binding-filtering/display-data.html). Just bear in mind that the first child of the Container you bind the Collection to will be the repeated item. With that in mind, you might want the first child Element to be another Container with a number of items inside like a title, description, button or image. ## Step 4: Update a record in Supabase From 254f4d4621b2c54a3d6d3a55fca37d73f10c40fe Mon Sep 17 00:00:00 2001 From: dannykng Date: Thu, 22 Dec 2022 16:45:20 -0800 Subject: [PATCH 203/215] Fix link to Snaplet docs --- apps/docs/pages/guides/integrations/snaplet.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/integrations/snaplet.mdx b/apps/docs/pages/guides/integrations/snaplet.mdx index 6f541f8253b..151b6627035 100644 --- a/apps/docs/pages/guides/integrations/snaplet.mdx +++ b/apps/docs/pages/guides/integrations/snaplet.mdx @@ -51,7 +51,7 @@ The password is the same password you used when creating the Supabase project. ![supabase-connection-db-info](/docs/img/guides/integrations/snaplet/supabase-connection-db-info.png) -You’ll have to confirm providing Snaplet access to your database. Snaplet will prompt you to only provide `read-only` access to your database. Snaplet has a guide in their documentation on how to do so [here](https://docs.snaplet.dev/postgresql/create-read-only-role). +You’ll have to confirm providing Snaplet access to your database. Snaplet will prompt you to only provide `read-only` access to your database. Snaplet has a guide in their documentation on how to do so [here](https://docs.snaplet.dev/guides/postgresql/#create-read-only-role). > Note that whatever connection string you provide here will be that of your Data Source – essentially the production database in a real-life scenario From b046bb1cc89d42886d9c8ec37788c7025d2a374e Mon Sep 17 00:00:00 2001 From: dannykng Date: Thu, 22 Dec 2022 16:45:40 -0800 Subject: [PATCH 204/215] Fix link to Sequin console --- apps/docs/pages/guides/integrations/sequin.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/integrations/sequin.mdx b/apps/docs/pages/guides/integrations/sequin.mdx index efdb578c775..200ca829ea0 100644 --- a/apps/docs/pages/guides/integrations/sequin.mdx +++ b/apps/docs/pages/guides/integrations/sequin.mdx @@ -82,7 +82,7 @@ You'll use this client to query for data in your synced schema. ## Resources - [Sequin](https://sequin.io) official website. -- [Sequin Console](https://app.sequin.io*). +- [Sequin Console](https://app.sequin.io). - [Sequin](https://docs.sequin.io/welcome) documentation. - [Sequin + Supabase + Stripe Tutorial](https://github.com/sequin-io/build-a-saas-with-next-js-supabase-stripe-and-sequin) From 9cee81b3a3dad4f04a00e636db9744d77812a45f Mon Sep 17 00:00:00 2001 From: Wen Bo Xie Date: Thu, 22 Dec 2022 16:47:53 -0800 Subject: [PATCH 205/215] chore: update license route from master to main branch --- apps/docs/pages/getting-started.mdx | 2 +- apps/docs/pages/guides/getting-started/architecture.mdx | 2 +- .../2022-04-01-supabase-realtime-with-multiplayer-features.mdx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/pages/getting-started.mdx b/apps/docs/pages/getting-started.mdx index 6313b6cb018..ccb7790f15e 100644 --- a/apps/docs/pages/getting-started.mdx +++ b/apps/docs/pages/getting-started.mdx @@ -62,7 +62,7 @@ A scalable websocket engine for managing user Presence, broadcasting messages, a - Official Docs: [Supabase Realtime docs](/docs/guides/realtime) - Source code: [github.com/supabase/realtime](https://github.com/supabase/realtime) -- License: [Apache 2](https://github.com/supabase/realtime/blob/master/LICENSE) +- License: [Apache 2](https://github.com/supabase/realtime/blob/main/LICENSE) - Language: Elixir ### Storage API (large file storage) diff --git a/apps/docs/pages/guides/getting-started/architecture.mdx b/apps/docs/pages/guides/getting-started/architecture.mdx index 91048898791..c97053b0c54 100644 --- a/apps/docs/pages/guides/getting-started/architecture.mdx +++ b/apps/docs/pages/guides/getting-started/architecture.mdx @@ -63,7 +63,7 @@ A scalable websocket engine for managing user Presence, broadcasting messages, a - Official Docs: [Supabase Realtime docs](/docs/guides/realtime) - Source code: [github.com/supabase/realtime](https://github.com/supabase/realtime) -- License: [Apache 2](https://github.com/supabase/realtime/blob/master/LICENSE) +- License: [Apache 2](https://github.com/supabase/realtime/blob/main/LICENSE) - Language: Elixir ### Storage API (large file storage) diff --git a/apps/www/_blog/2022-04-01-supabase-realtime-with-multiplayer-features.mdx b/apps/www/_blog/2022-04-01-supabase-realtime-with-multiplayer-features.mdx index e86f6423582..63600d109ee 100644 --- a/apps/www/_blog/2022-04-01-supabase-realtime-with-multiplayer-features.mdx +++ b/apps/www/_blog/2022-04-01-supabase-realtime-with-multiplayer-features.mdx @@ -76,4 +76,4 @@ Tech stack: Sign up for the waitlist at [multiplayer.dev](https://multiplayer.dev). -You can follow development in our [open source repository on GitHub](https://github.com/supabase/realtime/tree/multiplayer). +You can follow development in our [open source repository on GitHub](https://github.com/supabase/realtime/tree/develop). From d57ce33748d048482e528357fd11d6278ae7b4b8 Mon Sep 17 00:00:00 2001 From: Qiao Han Date: Fri, 23 Dec 2022 10:04:24 +0800 Subject: [PATCH 206/215] fix: pass down postgres config to self-hosted db --- docker/docker-compose.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 659c501cde3..c3efd687b65 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -197,9 +197,14 @@ services: - log_min_messages=fatal # prevents Realtime polling queries from appearing in logs restart: unless-stopped ports: - - ${POSTGRES_PORT}:5432 + - ${POSTGRES_PORT}:${POSTGRES_PORT} environment: POSTGRES_HOST: /var/run/postgresql + PGPORT: ${POSTGRES_PORT} + POSTGRES_PORT: ${POSTGRES_PORT} + PGPASSWORD: ${POSTGRES_PASSWORD} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + PGDATABASE: ${POSTGRES_DB} + POSTGRES_DB: ${POSTGRES_DB} volumes: - ./volumes/db/roles.sql:/docker-entrypoint-initdb.d/roles.sql From 93f00f5a0fa8f1cb34b5bb1b106f34b2004a2682 Mon Sep 17 00:00:00 2001 From: Qiao Han Date: Fri, 23 Dec 2022 10:22:18 +0800 Subject: [PATCH 207/215] chore: add cli team as code owners for self-hosted --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 57849f5e09a..7e2b99fa05d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,3 +2,4 @@ /apps/www/ @supabase/frontend /apps/docs/ @supabase/docs /spec/ @supabase/docs +/docker/ @supabase/cli From 60b29b7246d19d7eb59dd46611de2b41a8abbfc7 Mon Sep 17 00:00:00 2001 From: Qiao Han Date: Fri, 23 Dec 2022 10:34:43 +0800 Subject: [PATCH 208/215] chore: comment on passing down pg port --- docker/docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index c3efd687b65..2eca99b5ab3 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -197,6 +197,7 @@ services: - log_min_messages=fatal # prevents Realtime polling queries from appearing in logs restart: unless-stopped ports: + # Pass down internal port because it's set dynamically by other services - ${POSTGRES_PORT}:${POSTGRES_PORT} environment: POSTGRES_HOST: /var/run/postgresql From 32b8c45e87dd6003caa882947b7d4abf2f712afd Mon Sep 17 00:00:00 2001 From: Jonathan Summers-Muir Date: Fri, 23 Dec 2022 11:57:12 +0800 Subject: [PATCH 209/215] simplify text display --- .../components/reference/RefFunctionSection.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/docs/components/reference/RefFunctionSection.tsx b/apps/docs/components/reference/RefFunctionSection.tsx index 18c753a52ef..ee681b84082 100644 --- a/apps/docs/components/reference/RefFunctionSection.tsx +++ b/apps/docs/components/reference/RefFunctionSection.tsx @@ -153,6 +153,14 @@ const RefFunctionSection: React.FC = (props) => { label={example.name} className="flex flex-col gap-3" > + {example.description && ( +
    + + {example.description} + +
    + )} + {((tables && tables.length > 0) || sql) && ( = (props) => { .replace('ts', ''))} - {example.description && ( -
    -
    Example Notes
    - - {example.description} - -
    - )} - {response && ( Date: Thu, 22 Dec 2022 11:07:19 +0700 Subject: [PATCH 210/215] Hide deprecated extensions --- .../interfaces/Database/Extensions/Extensions.constants.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/studio/components/interfaces/Database/Extensions/Extensions.constants.ts b/studio/components/interfaces/Database/Extensions/Extensions.constants.ts index 6d8dfbb1e66..669b441d503 100644 --- a/studio/components/interfaces/Database/Extensions/Extensions.constants.ts +++ b/studio/components/interfaces/Database/Extensions/Extensions.constants.ts @@ -12,4 +12,6 @@ export const HIDDEN_EXTENSIONS = [ 'pg_visibility', 'pgstattuple', 'supabase_vault', + 'intagg', + 'xml2', ] From 8db1749e811455d8372f0812b071f3b81262caf9 Mon Sep 17 00:00:00 2001 From: Qiao Han Date: Fri, 23 Dec 2022 11:22:38 +0800 Subject: [PATCH 211/215] chore: remove connection string from self-hosted response --- .../AuthSelectionRenderer.tsx | 3 +- studio/lib/api/apiHelpers.ts | 55 ------------------- studio/package.json | 2 - studio/pages/api/profile/index.ts | 14 +---- studio/pages/api/projects/[ref]/index.ts | 33 +---------- 5 files changed, 3 insertions(+), 104 deletions(-) diff --git a/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/AuthSelectionRenderer.tsx b/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/AuthSelectionRenderer.tsx index aaeaf7518c8..1155d4aedc6 100644 --- a/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/AuthSelectionRenderer.tsx +++ b/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/AuthSelectionRenderer.tsx @@ -1,6 +1,5 @@ -import { parse } from 'crypto-js/enc-utf8' import { tryParseJson } from 'lib/helpers' -import { isUnixMicro, PreviewLogData, unixMicroToIsoTimestamp } from '..' +import { PreviewLogData } from '..' import { LOGS_TAILWIND_CLASSES } from '../Logs.constants' import { jsonSyntaxHighlight, diff --git a/studio/lib/api/apiHelpers.ts b/studio/lib/api/apiHelpers.ts index f66452e6a52..a6d23bf77ec 100644 --- a/studio/lib/api/apiHelpers.ts +++ b/studio/lib/api/apiHelpers.ts @@ -1,6 +1,4 @@ import { camelCase, snakeCase } from 'lodash' -import AES from 'crypto-js/aes' -import Utf8 from 'crypto-js/enc-utf8' import { IS_PLATFORM } from 'lib/constants' /** @@ -104,59 +102,6 @@ export const toCamelCase = (object, whitelist = []) => { } } -/** - * Decrypts any string using the supabase encryption key - */ -const decryptString = (stringToDecrypt: string) => { - const key = process.env.SUPABASE_ENCRYPTION_KEY - if (key) { - return AES.decrypt(stringToDecrypt, key).toString(Utf8) - } else { - return stringToDecrypt - } -} - -/** - * Encrypts any string using the supabase encryption key - */ -const encryptString = (stringToEncrypt: string) => { - const key = process.env.SUPABASE_ENCRYPTION_KEY - if (key) { - return AES.encrypt(stringToEncrypt, key).toString() - } else { - return stringToEncrypt - } -} - -/** - * Creates a Postgres connection string using the Supabase master login. - * Expects the passwords to be encrypted (straight from the DB) - */ -export const createEncryptedDbConnectionString = ({ - db_user_supabase, - db_pass_supabase, - db_dns_name, - db_host, - db_port, - db_name, - db_ssl, -}: { - db_user_supabase: string - db_host: string - db_pass_supabase: string - db_dns_name?: String - db_port: number - db_name: string - db_ssl: boolean -}) => { - const host = !db_dns_name ? db_host : db_dns_name // check IP already assigned - const unencrypted = decryptString(db_pass_supabase) - const value = `postgres://${db_user_supabase}:${unencrypted}@${host}:${db_port}/${db_name}?sslmode=${ - db_ssl ? 'require' : 'disable' - }` - return encryptString(value) -} - /** * Moves all Namespaced variables to the root * diff --git a/studio/package.json b/studio/package.json index 547db163b08..e35239fa471 100644 --- a/studio/package.json +++ b/studio/package.json @@ -40,7 +40,6 @@ "common": "*", "config": "*", "configcat-js": "^5.6.0", - "crypto-js": "^4.1.1", "dayjs": "^1.11.0", "file-saver": "^2.0.5", "generate-password": "^1.7.0", @@ -108,7 +107,6 @@ "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^13.5.0", "@types/blueimp-md5": "^2.18.0", - "@types/crypto-js": "^4.0.2", "@types/file-saver": "^2.0.2", "@types/json-logic-js": "^1.2.1", "@types/lodash": "^4.14.172", diff --git a/studio/pages/api/profile/index.ts b/studio/pages/api/profile/index.ts index 6b25f3d3396..4b88292532a 100644 --- a/studio/pages/api/profile/index.ts +++ b/studio/pages/api/profile/index.ts @@ -1,8 +1,6 @@ import { NextApiRequest, NextApiResponse } from 'next' -import { IS_PLATFORM } from 'lib/constants' import apiWrapper from 'lib/api/apiWrapper' -import { createEncryptedDbConnectionString } from 'lib/api/apiHelpers' export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) @@ -41,17 +39,7 @@ const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { cloud_provider: 'localhost', status: 'ACTIVE_HEALTHY', region: 'local', - connectionString: IS_PLATFORM - ? createEncryptedDbConnectionString({ - db_user_supabase: 'postgres', - db_dns_name: undefined, - db_host: 'localhost', - db_pass_supabase: String(process.env.POSTGRES_PASSWORD), - db_port: 5432, - db_name: 'postgres', - db_ssl: false, - }) - : '', + connectionString: '', }, ], }, diff --git a/studio/pages/api/projects/[ref]/index.ts b/studio/pages/api/projects/[ref]/index.ts index 7ae98b66a38..8c83076216c 100644 --- a/studio/pages/api/projects/[ref]/index.ts +++ b/studio/pages/api/projects/[ref]/index.ts @@ -27,41 +27,10 @@ const handleGet = async (req: NextApiRequest, res: NextApiResponse) => { cloud_provider: 'localhost', status: 'ACTIVE_HEALTHY', region: 'local', - connectionString: createDbConnectionString({ - db_user_supabase: 'postgres', - db_host: 'localhost', - db_pass_supabase: process.env.POSTGRES_PASSWORD as string, - db_port: 5432, - db_name: 'postgres', - db_ssl: false, - }), + connectionString: '', kpsVersion: 'kps-v1.0.0', restUrl: PROJECT_REST_URL, } return res.status(200).json(response) } - -/** - * Creates a Postgres connection string using the Supabase master login. - * Expects the passwords to be encrypted (straight from the DB) - */ -const createDbConnectionString = ({ - db_user_supabase, - db_pass_supabase, - db_host, - db_port, - db_name, - db_ssl, -}: { - db_user_supabase: string - db_host: string - db_pass_supabase: string - db_port: number - db_name: string - db_ssl: boolean -}) => { - return `postgres://${db_user_supabase}:${db_pass_supabase}@${db_host}:${db_port}/${db_name}?sslmode=${ - db_ssl ? 'require' : 'disable' - }` -} From 4e473315953c06fbac1340f4680588daf88c297f Mon Sep 17 00:00:00 2001 From: Qiao Han Date: Fri, 23 Dec 2022 11:22:57 +0800 Subject: [PATCH 212/215] chore: remove dependency on crypto-js --- package-lock.json | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2f08bafa685..a623b4950f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9993,12 +9993,6 @@ "classnames": "*" } }, - "node_modules/@types/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==", - "dev": true - }, "node_modules/@types/d3-color": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-2.0.3.tgz", @@ -14360,11 +14354,6 @@ "node": "*" } }, - "node_modules/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" - }, "node_modules/css-blank-pseudo": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", @@ -37604,7 +37593,6 @@ "common": "*", "config": "*", "configcat-js": "^5.6.0", - "crypto-js": "^4.1.1", "dayjs": "^1.11.0", "file-saver": "^2.0.5", "generate-password": "^1.7.0", @@ -37672,7 +37660,6 @@ "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^13.5.0", "@types/blueimp-md5": "^2.18.0", - "@types/crypto-js": "^4.0.2", "@types/file-saver": "^2.0.2", "@types/json-logic-js": "^1.2.1", "@types/lodash": "^4.14.172", @@ -44750,12 +44737,6 @@ "classnames": "*" } }, - "@types/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==", - "dev": true - }, "@types/d3-color": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-2.0.3.tgz", @@ -48306,11 +48287,6 @@ "randomfill": "^1.0.3" } }, - "crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" - }, "css-blank-pseudo": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", @@ -63396,7 +63372,6 @@ "@testing-library/react-hooks": "*", "@testing-library/user-event": "^13.5.0", "@types/blueimp-md5": "^2.18.0", - "@types/crypto-js": "^4.0.2", "@types/file-saver": "^2.0.2", "@types/json-logic-js": "^1.2.1", "@types/lodash": "^4.14.172", @@ -63427,7 +63402,6 @@ "common": "*", "config": "*", "configcat-js": "^5.6.0", - "crypto-js": "^4.1.1", "dayjs": "^1.11.0", "file-saver": "^2.0.5", "generate-password": "^1.7.0", From 7ae5e415d99501239aeca4345342bade8625c215 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 22 Dec 2022 11:24:10 +0700 Subject: [PATCH 213/215] Update saving indicator for table editor --- .../grid/components/header/Header.tsx | 5 +- .../grid/components/header/RefreshButton.tsx | 49 ++++++++--- .../grid/components/header/StatusLabel.tsx | 81 ------------------- 3 files changed, 41 insertions(+), 94 deletions(-) delete mode 100644 studio/components/grid/components/header/StatusLabel.tsx diff --git a/studio/components/grid/components/header/Header.tsx b/studio/components/grid/components/header/Header.tsx index a3042f00fe0..f84f7d50b33 100644 --- a/studio/components/grid/components/header/Header.tsx +++ b/studio/components/grid/components/header/Header.tsx @@ -41,10 +41,7 @@ const Header: FC = ({ sorts, filters, onAddColumn, onAddRow, header onAddRow={onAddRow} /> )} -
    - {headerActions} - -
    +
    {headerActions}
    ) } diff --git a/studio/components/grid/components/header/RefreshButton.tsx b/studio/components/grid/components/header/RefreshButton.tsx index 518a5f8b721..cea80d66f2e 100644 --- a/studio/components/grid/components/header/RefreshButton.tsx +++ b/studio/components/grid/components/header/RefreshButton.tsx @@ -1,5 +1,6 @@ -import { FC, useState } from 'react' -import { Button, IconRefreshCw } from 'ui' +import { FC, useState, useEffect } from 'react' +import { Button, IconRefreshCw, IconCheck } from 'ui' +import { SupabaseGridQueue } from '../../constants' import { Filter, Sort } from 'components/grid/types' import { useDispatch, useTrackedState } from 'components/grid/store' import { fetchCount, fetchPage } from 'components/grid/utils' @@ -10,10 +11,34 @@ interface Props { } const RefreshButton: FC = ({ sorts, filters }) => { - const [loading, setLoading] = useState(false) const state = useTrackedState() const dispatch = useDispatch() + const [loading, setLoading] = useState(false) + const [status, setStatus] = useState() + + useEffect(() => { + let isMounted = true + let timer: number | null + + SupabaseGridQueue.on('active', () => { + if (timer) clearTimeout(timer) + + if (isMounted) setStatus('saving') + }) + SupabaseGridQueue.on('idle', () => { + if (timer) clearTimeout(timer) + timer = window.setTimeout(() => setStatus(undefined), 2000) + + if (isMounted) setStatus('saved') + }) + + return () => { + isMounted = false + if (timer) clearTimeout(timer) + } + }, []) + async function onClick() { setLoading(true) await fetchCount(state, dispatch, filters) @@ -25,14 +50,20 @@ const RefreshButton: FC = ({ sorts, filters }) => {
    + !status ? ( + + ) : status === 'saved' ? ( + + ) : ( + <> + ) } - onClick={onClick} - loading={loading} + onClick={() => { + if (!status) onClick() + }} + loading={loading || status === 'saving'} > - Refresh + {!status ? 'Refresh' : status === 'saved' ? 'Changes saved' : 'Saving changes'} ) } diff --git a/studio/components/grid/components/header/StatusLabel.tsx b/studio/components/grid/components/header/StatusLabel.tsx deleted file mode 100644 index 21050e11696..00000000000 --- a/studio/components/grid/components/header/StatusLabel.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { useState, FC, useEffect } from 'react' -import * as Tooltip from '@radix-ui/react-tooltip' -import { IconLoader, IconCheck } from 'ui' -import { SupabaseGridQueue } from '../../constants' - -interface Props {} - -const StatusLabel: FC = ({}) => { - const [status, setStatus] = useState() - - useEffect(() => { - let isMounted = true - let timer: number | null - - SupabaseGridQueue.on('active', () => { - if (timer) clearTimeout(timer) - - if (isMounted) setStatus('saving') - }) - SupabaseGridQueue.on('idle', () => { - if (timer) clearTimeout(timer) - timer = window.setTimeout(() => setStatus(undefined), 4000) - - if (isMounted) setStatus('saved') - }) - - return () => { - isMounted = false - if (timer) clearTimeout(timer) - } - }, []) - - return ( -
    - {status === 'saving' ? ( - - - - - - -
    - Saving changes... -
    -
    -
    - ) : status === 'saved' ? ( - - - - - - -
    - All changes saved -
    -
    -
    - ) : ( - <> - )} - - {!status && ( -
    -
    -
    - )} -
    - ) -} -export default StatusLabel From 8f448abe55a3da318daf86cc5e1000b85cda6d38 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 22 Dec 2022 11:24:34 +0700 Subject: [PATCH 214/215] Remove import --- studio/components/grid/components/header/Header.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/studio/components/grid/components/header/Header.tsx b/studio/components/grid/components/header/Header.tsx index f84f7d50b33..4ad75e4eaef 100644 --- a/studio/components/grid/components/header/Header.tsx +++ b/studio/components/grid/components/header/Header.tsx @@ -5,7 +5,6 @@ import { saveAs } from 'file-saver' import { useStore } from 'hooks' import FilterDropdown from './filter' import SortPopover from './sort' -import StatusLabel from './StatusLabel' import RefreshButton from './RefreshButton' import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal' import { Sort, Filter } from 'components/grid/types' From 3743e4574b8f40a83af512caa6699015603946d5 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 23 Dec 2022 14:31:23 +0700 Subject: [PATCH 215/215] Implement optimistic rendering for editing rows in the grid itself --- .../formatter/ForeignKeyFormatter.tsx | 2 +- studio/components/grid/components/grid/Grid.tsx | 17 +++++++++++------ .../grid/services/row/SqlRowService.ts | 14 +++++++++++++- studio/components/grid/services/row/index.tsx | 1 + 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx b/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx index edb9b9084c3..e6abb30516f 100644 --- a/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx +++ b/studio/components/grid/components/formatter/ForeignKeyFormatter.tsx @@ -17,7 +17,7 @@ export const ForeignKeyFormatter = ( const rowData = deepClone(p.row) rowData[p.column.key] = _value - const { error } = state.rowService!.update(rowData, p.column.key) + const { error } = state.rowService!.update(rowData, p.row, p.column.key) if (error) { if (state.onError) state.onError(error) } else { diff --git a/studio/components/grid/components/grid/Grid.tsx b/studio/components/grid/components/grid/Grid.tsx index 230e40dabf3..07aee22745b 100644 --- a/studio/components/grid/components/grid/Grid.tsx +++ b/studio/components/grid/components/grid/Grid.tsx @@ -41,12 +41,17 @@ export const Grid = memo( ) if (changedColumn) { - const { error } = state.rowService!.update(rowData, changedColumn, (payload) => { - dispatch({ - type: 'EDIT_ROW', - payload, - }) - }) + const { error } = state.rowService!.update( + rowData, + originRowData, + changedColumn, + (payload) => { + dispatch({ + type: 'EDIT_ROW', + payload, + }) + } + ) if (error && onErrorFunc) onErrorFunc(error) } } diff --git a/studio/components/grid/services/row/SqlRowService.ts b/studio/components/grid/services/row/SqlRowService.ts index 4e4e7226396..5014d78df00 100644 --- a/studio/components/grid/services/row/SqlRowService.ts +++ b/studio/components/grid/services/row/SqlRowService.ts @@ -163,12 +163,21 @@ export class SqlRowService implements IRowService { return rows } - update(row: SupaRow, changedColumn?: string, onRowUpdate?: (value: any) => void) { + update( + row: SupaRow, + originalRow: SupaRow, + changedColumn?: string, + onRowUpdate?: (value: any) => void + ) { const { primaryKeys, error } = this.getPrimaryKeys() if (error) { return { error } } const { idx, ...value } = row + + // Optimistic rendering + if (onRowUpdate) onRowUpdate({ row: value, idx }) + const matchValues: any = {} primaryKeys!.forEach((key) => { matchValues[key] = row[key] @@ -199,6 +208,9 @@ export class SqlRowService implements IRowService { if (error) throw error if (onRowUpdate) onRowUpdate({ row: data[0], idx }) }).catch((error) => { + const { idx, ...originalRowData } = originalRow + // Revert optimistic rendering if any errors + if (onRowUpdate) onRowUpdate({ row: originalRowData, idx }) this.onError(error) }) diff --git a/studio/components/grid/services/row/index.tsx b/studio/components/grid/services/row/index.tsx index 3c63b0685a1..fd60208cba0 100644 --- a/studio/components/grid/services/row/index.tsx +++ b/studio/components/grid/services/row/index.tsx @@ -28,6 +28,7 @@ export interface IRowService { update: ( row: SupaRow, + originalRow: SupaRow, changedColumn?: string, onRowUpdate?: (value: any) => void ) => { row?: SupaRow; error?: ServiceError }