diff --git a/.gitignore b/.gitignore index c67a911193a..8e245433c33 100644 --- a/.gitignore +++ b/.gitignore @@ -121,3 +121,5 @@ typings/ **/supabase/.temp apps/new-docs/* + +gcloud.json \ No newline at end of file diff --git a/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx index 1798c82798d..db93e1616a2 100644 --- a/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx @@ -115,6 +115,13 @@ const home = [ level: 'reference_csharp', community: true, }, + { + label: 'Swift', + icon: '/img/icons/menu/reference-swift', + href: '/reference/swift/introduction', + level: 'reference_swift', + community: true, + }, { label: 'Tools', }, diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 6584e64d2e4..a2f78fc985d 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -28,6 +28,12 @@ export const REFERENCES: References = { versions: ['v0'], icon: '/docs/img/libraries/c-sharp-icon.svg', }, + swift: { + name: 'Swift', + library: 'supabase-swift', + versions: ['v1'], + icon: '/docs/img/libraries/swift-icon.svg', + }, cli: { name: 'CLI', library: undefined, @@ -284,7 +290,7 @@ export const auth = { url: '/guides/auth/enterprise-sso', items: [ { - name: 'SAML 2.0 (Beta)', + name: 'SAML 2.0', url: '/guides/auth/sso/auth-sso-saml', }, ], @@ -828,6 +834,13 @@ export const self_hosting = { { name: 'Configuration', url: '/guides/self-hosting/realtime/config' }, ], }, + { + name: 'Analytics Server', + items: [ + { name: 'Reference', url: '/reference/self-hosting-analytics/introduction', items: [] }, + { name: 'Configuration', url: '/guides/self-hosting/analytics/config', items: [] }, + ], + }, ], } @@ -937,6 +950,13 @@ export const reference = { level: 'reference_python', icon: '/img/icons/menu/reference-python', }, + { + name: 'supbase-swift', + url: '/reference/swift/start', + level: 'reference_swift', + items: [], + icon: '/img/icons/menu/reference-swift', + }, // { // name: 'supabase-python', // url: '/reference/python/start', @@ -1006,6 +1026,13 @@ export const reference_python_v2 = { parent: '/reference', } +export const reference_swift_v1 = { + icon: 'reference-swift', + title: 'swift', + url: 'guides/reference/swift', + parent: '/reference', +} + export const reference_cli = { icon: 'reference-cli', title: 'Supabase CLI', @@ -1034,12 +1061,19 @@ export const reference_self_hosting_storage = { } export const reference_self_hosting_realtime = { - icon: 'reference-auth', + icon: 'reference-realtime', title: 'Self-Hosting Realtime', url: '/guides/reference/self-hosting/realtime', parent: '/reference', } +export const reference_self_hosting_analytics = { + icon: 'reference-analytics', + title: 'Self-Hosting Analytics', + url: '/guides/reference/self-hosting/analytics', + parent: '/reference', +} + // export const reference: [ // { // label: 'Official' @@ -1106,6 +1140,13 @@ export const references = [ icon: '/docs/img/icons/c-sharp-icon.svg', url: '/reference/csharp/start', }, + { + label: 'supabase-swift', + versions: ['v1'], + description: 'something about the reference', + icon: '/docs/img/icons/swift-icon.svg', + url: '/reference/swift/start', + }, ], }, { diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx index 0b481973cb8..945d817abcf 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx @@ -15,6 +15,8 @@ import spec_dart_v0 from '~/../../spec/supabase_dart_v0.yml' assert { type: 'yml import spec_csharp_v0 from '~/../../spec/supabase_csharp_v0.yml' assert { type: 'yml' } // @ts-expect-error import spec_python_v2 from '~/../../spec/supabase_py_v2.yml' assert { type: 'yml' } +// @ts-expect-error +import spec_swift_v1 from '~/../../spec/supabase_swift_v1.yml' assert { type: 'yml' } // import { gen_v3 } from '~/lib/refGenerator/helpers' import apiCommonSections from '~/../../spec/common-api-sections.json' @@ -22,6 +24,7 @@ import cliCommonSections from '~/../../spec/common-cli-sections.json' import libCommonSections from '~/../../spec/common-client-libs-sections.json' import authServerCommonSections from '~/../../spec/common-self-hosting-auth-sections.json' import realtimeServerCommonSections from '~/../../spec/common-self-hosting-realtime-sections.json' +import analyticsServerCommonSections from '~/../../spec/common-self-hosting-analytics-sections.json' import storageServerCommonSections from '~/../../spec/common-self-hosting-storage-sections.json' import { flattenSections } from '~/lib/helpers' import NavigationMenuHome from './HomeMenu' @@ -54,22 +57,26 @@ export type RefIdOptions = | 'reference_dart_v1' | 'reference_csharp_v0' | 'reference_python_v2' + | 'reference_swift_v1' | 'reference_cli' | 'reference_api' | 'reference_self_hosting_auth' | 'reference_self_hosting_storage' | 'reference_self_hosting_realtime' + | 'reference_self_hosting_analytics' export type RefKeyOptions = | 'javascript' | 'dart' | 'csharp' | 'python' + | 'swift' | 'cli' | 'api' | 'self-hosting-auth' | 'self-hosting-storage' | 'self-hosting-realtime' + | 'self-hosting-analytics' const NavigationMenu = () => { const router = useRouter() @@ -135,10 +142,14 @@ const NavigationMenu = () => { case url.includes(`/docs/reference/csharp`) && url: menuState.setMenuLevelId('reference_csharp_v0') break - // puthon v2 (latest) + // python v2 (latest) case url.includes(`/docs/reference/python`) && url: menuState.setMenuLevelId('reference_python_v2') break + // swift v1 (latest) + case url.includes(`/docs/reference/swift`) && url: + menuState.setMenuLevelId('reference_swift_v1') + break case url.includes(`/docs/reference/cli/config`) && url: menuState.setMenuLevelId('supabase_cli') break @@ -157,6 +168,9 @@ const NavigationMenu = () => { case url.includes(`/docs/reference/self-hosting-realtime`) && url: menuState.setMenuLevelId('reference_self_hosting_realtime') break + case url.includes(`/docs/reference/self-hosting-analytics`) && url: + menuState.setMenuLevelId('reference_self_hosting_analytics') + break default: break @@ -195,11 +209,13 @@ const NavigationMenu = () => { const isReference_Dart_V1 = 'reference_dart_v1' === level const isReference_Csharp_V0 = 'reference_csharp_v0' === level const isReference_Python_V2 = 'reference_python_v2' === level + const isReference_Swift_V1 = 'reference_swift_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 + const isReference_Self_Hosting_Analytics = 'reference_self_hosting_analytics' === level return (
@@ -263,7 +279,14 @@ const NavigationMenu = () => { lib="csharp" spec={spec_csharp_v0} /> - + { commonSections={realtimeServerCommonSections} lib="self-hosting-realtime" /> +
) } diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenuGuideList.tsx index 28c235ca641..fe3ea4e290e 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, value }) => { type={value ? 'multiple' : 'single'} value={value ?? firstLevelRoute} className={[ - 'transition-all ml-8 duration-150 ease-out', + 'transition-all duration-150 ease-out', // enabled active && 'opacity-100 ml-0 delay-150', // level === 'home' && 'ml-12', diff --git a/apps/docs/docs/ref/self-hosting-analytics/introduction.mdx b/apps/docs/docs/ref/self-hosting-analytics/introduction.mdx new file mode 100644 index 00000000000..5d6efe41eda --- /dev/null +++ b/apps/docs/docs/ref/self-hosting-analytics/introduction.mdx @@ -0,0 +1,108 @@ +--- +id: introduction +title: Introduction +hideTitle: true +--- + +
+ +
+

Self-Hosting Analytics

+
+
+ + + + +The Supabase Analytics server is a Logflare self-hostable instance that manages the ingestion and query pipelines for searching and aggregating structured analytics events. + +When self-hosting the Analytics server, the full logging experience matching that of the Supabase Platform is available in the Studio instance, allowing for an integrated and enhanced development experience. +However, it's important to note that certain [differences](#differences) may arise due to the platform's infrastructure. + + + All logflare technical docs are available at + [https://docs.logflare.app](https://docs.logflare.app) + + +## Getting Started + +Using the example [self-hosting stack based on docker-compose](https://github.com/supabase/supabase/tree/master/docker), you include the logging related services using the following command + +```bash +# assuming you clone the supabase/supabase repo. +cd docker +docker compose -f docker-compose.yml -f docker-compose-logging.yml up +``` + +This would include two additional docker services to your compose stack: Logflare and Vector. + + + Read more about [self-hosting Logflare](https://docs.logflare.app/self-hosting) as your analytics + server. + + +## Differences + +API logs rely on Kong instead of the Supabase Cloud API Gateway. Logs from Kong are not enriched with platform-only data. + +Within the self-hosted setup, all logs are routed to Logflare via Vector. As Kong routes API requests to PostgREST, self-hosted or local deployments will result in Kong request logs instead. +This would result in differences in the log event metadata between self-hosted API requests and Supabase Platform requests. + +## BigQuery + +All log event data is stored in and queried from BigQuery. To use the Analytics server with Supabase you'll need a Google Cloud Platform account for access to BigQuery. + +Make sure to set the `GOOGLE_DATASET_ID_APPEND`, `GOOGLE_PROJECT_ID` and `GOOGLE_PROJECT_NUMBER` environment variables. + +Download your Google Cloud API JWT and store it under gcloud.json in your working directory. + + + You must also enable billing on your Google Cloud project, as the streaming inserts feature is + required. + + +## Production Recommendations + +To self-host in a production setting, we recommend performing the following for a better experience. + +### Ensure that Logflare is behind a firewall and restrict all network access to it besides safe requests. + +Self-hosted Logflare has UI authentication disabled and is intended for exposure to the internet. We recommend restricting access to the dashboard, accessible at the `/dashboard` path. +If dashboard access is required for managing sources, we recommend having an authentication layer, such as a VPN. + +### Use a different Postgres Database to store Logflare data. + +Logflare requires a Postgres database to function. However, if there is an issue with you self-hosted Postgres service, you would not be able to debug it as it would also bring Logflare down together. + +The self-hosted example is only used as a minimal example on running the entire stack, however it is not recommended to use the same database server for both production and observability. + + + + + + ### Client libraries + + - [JavaScript - Pino Transport](https://github.com/Logflare/pino-logflare) + - [Elixir](https://github.com/Logflare/logflare_api_client) + - [Elixir - Logger Backend](https://github.com/Logflare/logflare_logger_backend) + - [Erlang](https://github.com/Logflare/logflare_erl) + - [Erlang - Lager Backend](https://github.com/Logflare/logflare_lager_backend) + - [Cloudflare Worker](https://gist.github.com/chasers/c7a220e91820a1084b27fcfdb18ad6bd) + + ### Integrations + + - [Fly - Logs](https://github.com/Logflare/fly-log-shipper) + - [Vercel Integration - Logs](https://vercel.com/integrations/logflare) + - [Cloudflare App - Logs](https://www.cloudflare.com/apps/logflare/install) + + ### Additional links + + - [Source code](https://github.com/logflare/logflare) + - [Analytics guides](/docs/guides/analytics) - TODO + - [OpenAPI docs](https://logflare.app/api/openapi) + - [Supabase Acquires Logflare](https://supabase.com/blog/supabase-acquires-logflare) + - [Logflare self-hosting docs](https://docs.logflare.app/self-hosting) + + + + diff --git a/apps/docs/docs/ref/swift/introduction.mdx b/apps/docs/docs/ref/swift/introduction.mdx new file mode 100644 index 00000000000..80dc3584bd1 --- /dev/null +++ b/apps/docs/docs/ref/swift/introduction.mdx @@ -0,0 +1,29 @@ +--- +id: introduction +title: Introduction +hideTitle: true +--- + +
+ +
+

Swift Client Library

+

@supabase-community/supabase-swift

+
+
+ +
+This reference documents every object and method available in Supabase's Swift library, [supabase-swift](https://github.com/supabase-community/supabase-swift). You can use supabase-swift to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files. + +We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Swift projects. +
+ +
+

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

+ +

+ Huge thanks to official maintainer, [Maail](https://github.com/maail). +

+ +
+ diff --git a/apps/docs/layouts/SiteLayout.tsx b/apps/docs/layouts/SiteLayout.tsx index 55c4261779a..68c0b88d87b 100644 --- a/apps/docs/layouts/SiteLayout.tsx +++ b/apps/docs/layouts/SiteLayout.tsx @@ -38,6 +38,10 @@ const levelsData = { icon: '/docs/img/icons/menu/realtime', name: 'Realtime', }, + analytics: { + icon: '/docs/img/icons/menu/analytics', + name: 'Analytics', + }, storage: { icon: '/docs/img/icons/menu/storage', name: 'Storage', @@ -86,6 +90,10 @@ const levelsData = { icon: '/docs/img/icons/menu/reference-python', name: 'Python Reference v2.0', }, + reference_swift_v1: { + icon: '/docs/img/icons/menu/reference-swift', + name: 'Swift Reference v1.0', + }, reference_cli: { icon: '/docs/img/icons/menu/reference-cli', name: 'CLI Reference', @@ -106,6 +114,10 @@ const levelsData = { icon: '/docs/img/icons/menu/reference-realtime', name: 'Realtime Server Reference', }, + reference_self_hosting_analytics: { + icon: '/docs/img/icons/menu/reference-analytics', + name: 'Analytics Server Reference', + }, } const MobileHeader = memo(function MobileHeader() { diff --git a/apps/docs/lib/mdx/getConfig.tsx b/apps/docs/lib/mdx/getConfig.tsx index 8a014486212..d208d4243cc 100644 --- a/apps/docs/lib/mdx/getConfig.tsx +++ b/apps/docs/lib/mdx/getConfig.tsx @@ -4,6 +4,8 @@ import specStorageV0 from '~/../../spec/storage_v0_config.yaml' assert { type: ' import specRealtimeV0 from '~/../../spec/realtime_v0_config.yaml' assert { type: 'yml' } // @ts-expect-error import specAuthV1 from '~/../../spec/gotrue_v1_config.yaml' assert { type: 'yml' } +// @ts-expect-error +import specAnalyticsV0 from '~/../../spec/analytics_v0_config.yaml' assert { type: 'yml' } function getStorageConfigV0() { return { ...specStorageV0 } @@ -17,4 +19,8 @@ function getAuthConfigV1() { return { ...specAuthV1 } } -export { getStorageConfigV0, getRealtimeConfigV0, getAuthConfigV1 } +function getAnalyticsConfigV0() { + return { ...specAnalyticsV0 } +} + +export { getStorageConfigV0, getRealtimeConfigV0, getAuthConfigV1, getAnalyticsConfigV0 } diff --git a/apps/docs/package.json b/apps/docs/package.json index 6ef6744c7d2..cfda86a3831 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -35,7 +35,10 @@ "gen:supabase-js:v2": "npm-run-all gen:supabase-js:v2:ref", "gen:supabase-js:v2:ref": "ts-node ./generator/index.ts gen --type legacy --input ../../spec/supabase_js_v2.yml --output ./docs/reference/javascript/generated", "gen:realtime": "npm-run-all gen:realtime:config", - "gen:realtime:config": "ts-node ./generator/index.ts gen --type config --input ../../spec/realtime_v0_config.yaml --output ./docs/reference/realtime/generated/config.mdx" + "gen:realtime:config": "ts-node ./generator/index.ts gen --type config --input ../../spec/realtime_v0_config.yaml --output ./docs/reference/realtime/generated/config.mdx", + "gen:analytics": "npm-run-all gen:analytics:config gen:analytics:usage", + "gen:analytics:config": "ts-node ./generator/index.ts gen --type config --input ../../spec/analytics_v0_config.yaml --output ./docs/reference/analytics/generated/config.mdx", + "gen:analytics:usage": "ts-node ./generator/index.ts gen --type api --input ../../spec/transforms/analytics_v0_openapi_deparsed.json --output ./docs/reference/analytics/generated/usage.mdx" }, "dependencies": { "@algolia/autocomplete-js": "^1.7.2", diff --git a/apps/docs/pages/guides/api/rest/client-libs.mdx b/apps/docs/pages/guides/api/rest/client-libs.mdx index 4b6272e1f6d..5594ef78686 100644 --- a/apps/docs/pages/guides/api/rest/client-libs.mdx +++ b/apps/docs/pages/guides/api/rest/client-libs.mdx @@ -20,7 +20,7 @@ Supabase provides client libraries for the REST and Realtime APIs. Some librarie | `Language` | `Source Code` | `Documentation` | | ----------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------- | -| C# | [supabase-js](https://github.com/supabase-community/supabase-csharp) | [Docs](https://supabase.com/docs/reference/csharp/introduction) | +| C# | [supabase-csharp](https://github.com/supabase-community/supabase-csharp) | [Docs](https://supabase.com/docs/reference/csharp/introduction) | | Go | [supabase-go](https://github.com/supabase-community/supabase-go) | | | Kotlin | [supabase-kt](https://github.com/supabase-community/supabase-kt) | | | Python | [supabase-py](https://github.com/supabase-community/supabase-py) | [Docs](https://supabase.com/docs/reference/python/initializing) | diff --git a/apps/docs/pages/guides/auth/enterprise-sso.mdx b/apps/docs/pages/guides/auth/enterprise-sso.mdx index fbfb7b73915..5ca0dffcd0c 100644 --- a/apps/docs/pages/guides/auth/enterprise-sso.mdx +++ b/apps/docs/pages/guides/auth/enterprise-sso.mdx @@ -1,13 +1,11 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { - title: 'Enterprise SSO', + title: 'Enterprise Single Sign-On', description: 'Learn about Single Sign-On support in Supabase Auth for enterprise applications', } -Supabase Auth supports building enterprise applications that require Single Sign-On (SSO) authentication. At this time only [SSO with SAML 2.0](/guides/auth/sso/auth-sso-saml) is supported in an early beta. - -If you are interested in using SAML 2.0 SSO with your Supabase project, please [open a new support ticket](https://app.supabase.com/support/new). +Supabase Auth supports building enterprise applications that require Single Sign-On (SSO) authentication [with SAML 2.0](/docs/guides/auth/sso/auth-sso-saml). export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx b/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx index 4ef6f345206..d5598cc9c7a 100644 --- a/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx +++ b/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx @@ -2,28 +2,35 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'auth-sso-saml', - title: 'Single Sign-On with SAML 2.0', - description: 'Use Single Sign-On (SSO) authentication with SAML 2.0', + title: 'Single Sign-On with SAML 2.0 for Projects', + description: 'Use Single Sign-On (SSO) authentication on your project with SAML 2.0', video: 'https://www.youtube.com/v/em1cpOAXknM', } -Supabase Auth supports enterprise-level Single Sign-On (SSO) for any identity providers compatible with the using the SAML 2.0 protocol. +Supabase Auth supports enterprise-level Single Sign-On (SSO) for any identity providers compatible with the using the SAML 2.0 protocol. This is a non-exclusive list of supported identity providers: - -This is an early beta release of these APIs. CLI and Dashboard support for SSO is under development. +- Google Workspaces (formerly known as GSuite) +- Okta, Auth0 +- Microsoft Active Directory, Azure Active Directory, Microsoft Entra +- PingIdentity +- OneLogin -If you are comfortable using these APIs and would like to try out SSO with SAML 2.0 for your project, please [open a support ticket](https://app.supabase.com/support/new). +If you're having issues with identity provider software not on this list, please [open a support ticket](https://app.supabase.com/support/new). -These APIs are not expected to change before the feature is generally available, but we do reserve the right to modify them. Projects in the Beta will be notified of any changes. +## Prerequisites - +This guide requires the use of the [Supabase CLI](/docs/guides/cli). Please make sure you're using version v1.46.4 or higher. You can use `supabase -v` to see the currently installed version. + +SAML 2.0 support is disabled by default on Supabase projects. You can configure this on the [Auth Providers](https://app.supabase.com/project/_/auth/providers) page on your project. + +Please note that SAML 2.0 support is offered on tiers Pro and above. Check the [Pricing](https://supabase.com/pricing) page for more information. ## Terminology -The number of SAML and SSO acronyms can often overwhelming. Here's a glossary which you can refer back to at any time: +The number of SAML and SSO acronyms can often be overwhelming. Here's a glossary which you can refer back to at any time: - **Identity Provider**, **IdP**, or **IDP** - This is software that manages user accounts at a company or organization. It can verify the identity of a user and exchange that information with your Supabase project. Commonly used identity providers are: Microsoft Active Directory (Azure AD, Microsoft Entra), Okta, Google Workspaces (GSuite), PingIdentity, OneLogin, and many others. + An identity provider is a service that manages user accounts at a company or organization. It can verify the identity of a user and exchange that information with your Supabase project and other applications. It acts as a single source of truth for user identities and access rights. Commonly used identity providers are: Microsoft Active Directory (Azure AD, Microsoft Entra), Okta, Google Workspaces (GSuite), PingIdentity, OneLogin, and many others. There are also self-hosted and on-prem versions of identity providers, and sometimes they are accessible only by having access to a company VPN or being in a specific building. - **Service Provider**, **SP** This is the software that is asking for user information from an identity provider. In Supabase, this is your project's Auth server. - **Assertion** @@ -47,17 +54,20 @@ The number of SAML and SSO acronyms can often overwhelming. Here's a glossary wh Below is information about your project's SAML 2.0 configuration which you can share with the company or organization that you're trying to on-board. -| Name | Value | -| ------------ | --------------------------------------------------------- | -| EntityID | `https://.supabase.co/auth/v1/sso/saml/metadata` | -| Metadata URL | `https://.supabase.co/auth/v1/sso/saml/metadata` | -| ACS URL | `https://.supabase.co/auth/v1/sso/saml/acs` | -| SLO URL | `https://.supabase.co/auth/v1/sso/slo` | -| NameID | Required `emailAddress` or `persistent` | +| Name | Value | +| --------------------------- | ----------------------------------------------------------------------- | +| EntityID | `https://.supabase.co/auth/v1/sso/saml/metadata` | +| Metadata URL | `https://.supabase.co/auth/v1/sso/saml/metadata` | +| Metadata URL
(download) | `https://.supabase.co/auth/v1/sso/saml/metadata?download=true` | +| ACS URL | `https://.supabase.co/auth/v1/sso/saml/acs` | +| SLO URL | `https://.supabase.co/auth/v1/sso/slo` | +| NameID | Required `emailAddress` or `persistent` | Note that SLO (Single Logout) is not supported at this time with Supabase Auth as it is a rarely supported feature by identity providers. However, the URL is registered and advertised for when this does become available. -Append `?download=true` to the Metadata URL to get a downloadable Metadata XML file. +Append `?download=true` to the Metadata URL to download the Metadata XML file. This is useful in cases where the identity provider requires a file. + +Alternatively, you can use the `supabase sso info --project-ref ` command to get setup information for your project. ### User accounts and identities @@ -119,18 +129,7 @@ CREATE POLICY "View organization settings." ## Managing SAML 2.0 connections -### Prerequisites - -SSO support with SAML 2.0 is in an early beta release. This guide uses the following software which you need to install on your machine to configure your project: - -- [**cURL**](https://curl.se) - It is typically pre-installed in macOS and GNU/Linux distributions. -- [**jq**](https://stedolan.github.io/jq/) - You can install it with `brew install jq` on macOS or using your distribution's package manager. - -You would need access to two keys -- the `anon` and `service_role` key. You can obtain these on the [Project API Keys](https://app.supabase.com/project/_/settings/api) page in the dashboard. - -We publish an [OpenAPI specification](https://github.com/supabase/gotrue/blob/master/openapi.yaml) which you can refer to at any time. +Once you've enabled SAML 2.0 support on your project via the [Auth Providers](https://app.supabase.com/project/_/auth/providers) page in the dashboard, you can use the [Supabase CLI](/docs/guides/cli) to add, update, remove and view information about identity providers. ### Add a connection @@ -151,40 +150,34 @@ Commonly used SAML 2.0 Identity Providers that support Metadata URLs: Commonly used SAML 2.0 Identity Providers that only support Metadata XML files: - Google Workspaces (GSuite) +- Any self-hosted or on-prem identity provider behind a VPN -Once you've obtained the SAML 2.0 Metadata XML file or URL you can establish a connection with your project's Supabase Auth server by invoking this API: +Once you've obtained the SAML 2.0 Metadata XML file or URL you can establish a connection with your project's Supabase Auth server by using the [Supabase CLI](/docs/guides/cli): ```bash -curl -X POST \ - -H 'Content-Type: application/json' \ - -H 'apikey: ' \ - -H 'Authorization: Bearer ' \ - --data-binary '@/tmp/body.json' \ - 'https://.supabase.co/auth/v1/admin/sso/providers' -``` - -To create the `/tmp/body.json` file you can use this: - -```bash -jq --null-input \ - --arg metadata_url "https://..." \ - '{ "type": "saml", "metadata_url": $metadata_url, "domains": ["company.com"] }' \ - > /tmp/body.json +supabase sso add --type saml --project-ref \ + --metadata-url 'https://company.com/idp/saml/metadata' \ + --domains company.com ``` If you wish to use a Metadata XML file instead, you can use: ```bash -jq --null-input \ - -M \ - --rawfile metadata_file /path/to/metadata.xml \ - '{ "type": "saml", "metadata_xml": $metadata_file, "domains": ["company.com"] }' \ - > /tmp/body.json +supabase sso add --type saml --project-ref \ + --metadata-file /path/to/saml/metadata.xml \ + --domains company.com ``` -Once you've executed the cURL command with the correct body, you should see details about the registered SAML 2.0 Identity Provider. +This command will register a new identity provider with your project's Auth server. When successful, you will see the details of the provider such as it's SAML information and registered domains. -To initiate a sign-in request from your front-end application you can use: +Please note that only persons with write access to the project can register, update or remove identity providers. + +Once you've added an identity provider, users who have access to it can sign in to your application. With SAML 2.0 there are two ways that users can sign in to your project: + +- By signing-in from your application's user interface, commonly known as **SP (Service Provider) Initiated Flow** +- By clicking on an icon in the application menu on the company intranet or identity provider page, commonly known as **Identity Provider Initiated (IdP) Flow** + +To initiate a sign-in request from your application's user interface (i.e. the SP Initiated Flow), you can use: ```typescript supabase.auth.signInWithSSO({ @@ -192,11 +185,11 @@ supabase.auth.signInWithSSO({ }) ``` -Which will start the sign-in process using the SSO Identity Provider registered for the `company.com` domain name. If the SSO Identity Provider does not have an associated domain name, you can use `providerId` instead. +Calling this method starts the sign-in process using the identity provider registered for the `company.com` domain name. It is not required that identity providers be assigned one or multiple domain names, in which case you can use the provider's unique ID instead. ### Understanding attribute mappings -When a user signs in using the SAML 2.0 Single Sign-On protocol, an XML document called the SAML Assertion is exchanged between the Identity Provider and Supabase Auth. +When a user signs in using the SAML 2.0 Single Sign-On protocol, an XML document called the SAML Assertion is exchanged between the identity provider and Supabase Auth. This assertion contains information about the user's identity and other authentication information, such as: @@ -206,9 +199,9 @@ This assertion contains information about the user's identity and other authenti - Department or organization - Other attributes present in the users directory managed by the identity provider -Other than the unique ID of the user, SAML does not make it mandatory that any other attributes appear in the assertion. Identity Providers are configured about what user information is shared with your project. +With exception of the unique user ID, SAML does not require any other attributes in the assertion. Identity providers can be configured so that only select user information is shared with your project. -Your project can be configured to recognize these attributes and map them into your project's database using a JSON structure. This process is called attribute mapping, and varies according to the configuration of the Identity Provider. +Your project can be configured to recognize these attributes and map them into your project's database using a JSON structure. This process is called attribute mapping, and varies according to the configuration of the identity provider. For example, the following JSON structure configures attribute mapping for the `email` and `first_name` user identity properties. @@ -225,7 +218,14 @@ For example, the following JSON structure configures attribute mapping for the ` } ``` -You can include this structure in the `POST /auth/v1/admin/sso/providers` call under the `attribute_mapping` property. +When creating or updating an identity provider with the [Supabase CLI](/docs/guides/cli) you can include this JSON as a file with the `--attribute-mapping /path/to/attribute/mapping.json` flag. + +For example, to change the attribute mappings to an existing provider you can use: + +```bash +supabase sso update --project-ref \ + --attribute-mapping /path/to/attribute/mapping.json +``` Given a SAML 2.0 assertion that includes these attributes: @@ -268,24 +268,26 @@ Supabase Auth does not require specifying attribute mappings if you only need ac At this time it is not possible to have users without an email address, so SAML assertions without one will be rejected. -Most SAML 2.0 identity providers use LDAP attribute names. However, due to their variability and complexity operators of Identity Providers are able to customize both the `Name` and attribute value that is sent to Supabase Auth in an assertion. Please refer to the identity provider's documentation and contact the operator for details on what attributes are mapped for your project. +Most SAML 2.0 identity providers use Lightweight Directory Access Protocol (LDAP) attribute names. However, due to their variability and complexity operators of identity providers are able to customize both the `Name` and attribute value that is sent to Supabase Auth in an assertion. Please refer to the identity provider's documentation and contact the operator for details on what attributes are mapped for your project. ### Remove a connection -Once a connection to an identity provider is established, you can remove it by invoking the `DELETE` method on it: +Once a connection to an identity provider is established, you can remove it by running: ```bash -curl -X DELETE \ - -H 'Content-Type: application/json' \ - -H 'apikey: ' \ - -H 'Authorization: Bearer ' \ - 'https://.supabase.co/auth/v1/admin/sso/providers/' +supabase sso remove --project-ref ``` -Once a connection is removed, all user accounts from that identity provider will be immediately logged out. User information will remain in the system, but it will no longer be possible for any of those accounts to be accessed in the future, even if you add the connection again. +If successful, the details of the removed identity provider will be shown. All user accounts from that identity provider will be immediately logged out. User information will remain in the system, but it will no longer be possible for any of those accounts to be accessed in the future, even if you add the connection again. If you need to reassign those user accounts to another identity provider, please [open a support ticket](https://app.supabase.com/support/new). +A list of all registered identity providers can be displayed by running: + +```bash +supabase sso list --project-ref +``` + ### Update a connection You may wish to update settings about a connection to a SAML 2.0 identity provider. @@ -293,23 +295,69 @@ You may wish to update settings about a connection to a SAML 2.0 identity provid Commonly this is necessary when: - Cryptographic keys are rotated or have expired -- Metadata URL has changed, but is the same Identity Provider -- Other SAML 2.0 Metadata attributes have changed, but it is still the same Identity Provider +- Metadata URL has changed, but is the same identity provider +- Other SAML 2.0 Metadata attributes have changed, but it is still the same identity provider - You are updating the domains or attribute mapping -```bash -curl -X PUT \ - -H 'Content-Type: application/json' \ - -H 'apikey: ' \ - -H 'Authorization: Bearer ' \ - --data-binary '@/tmp/body.json' \ - 'https://.supabase.co/auth/v1/admin/sso/providers/' +You can use the following command to update the configuration of an identity provider: +```bash +supabase sso update --project-ref ``` -The request body has the same structure as when you're adding a connection to an Identity Provider. +Please use `--help` to see all available flags. -It is not possible to change the Identity Provider's unique SAML identifier known as `EntityID`. Everything else can be updated. If the SAML `EntityID` of your identity provider has changed, it is regarded as a new identity provider and you will have to register it like a new connection. +It is not possible to change the unique SAML identifier of the identity provider, known as `EntityID`. Everything else can be updated. If the SAML `EntityID` of your identity provider has changed, it is regarded as a new identity provider and you will have to register it like a new connection. + +### Retrieving information about a connection + +You can always obtain a list of all registered providers using: + +```bash +supabase sso list --project-ref +``` + +This list will only include basic information about each provider. To see all of the information about a provider you can use: + +```bash +supabase sso show --project-ref +``` + +You can use the `-o json` flag to output the information as JSON, should you need to. Other formats may be supported, please use `--help` to see all available options. + +## Frequently Asked Questions + +### How do I publish my application to an identity provider's marketplace? + +Many cloud-based identity providers offer a marketplace where you can register your application for easy on-boarding with customers. When you use Supabase Auth's SAML 2.0 support you can register your project in any one of these marketplaces. + +Please refer to the relevant documentation for each cloud-based identity provider on how you can do this. Some common marketplaces are: + +- [Okta Integration Network](https://developer.okta.com/docs/guides/build-sso-integration/saml2/main/) +- [Azure Active Directory App Gallery](https://learn.microsoft.com/en-us/azure/active-directory-b2c/publish-app-to-azure-ad-app-gallery) +- [Google Workspaces Pre-integrated SAML apps catalog](https://support.google.com/a/table/9217027) + +### Why do some users get: SAML Assertion does not contain email address? + +Identity providers do not have to send back and email address for the user, though they often do. Supabase Auth requires that an email address is present. + +The following list of commonly used SAML attribute names is inspected, in order of appearance, to discover the email address in the assertion: + +- `urn:oid:0.9.2342.19200300.100.1.3` +- `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` +- `http://schemas.xmlsoap.org/claims/EmailAddress` +- `mail` +- `email` + +Finally if there is no such attribute, it will use the SAML `NameID` value but only if the format is advertised as `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`. + +Should you run into this problem, it is most likely a misconfiguration issue **on the identity provider side.** Please instruct your contact at the company to map the user's email address to one of the above listed attribute names, typically `email`. + +### How do I access the private key used for SAML in my project? + +At this time it is not possible to extract the RSA private key used by your project's Supabase Auth server. This is done to keep the private key as secure as possible, given that SAML does not offer an easy way to rotate keys without disrupting service. (Please use a SAML 2.0 Metadata URL whenever possible for this reason!) + +If you really need access to the key, please [open a support ticket](https://app.supabase.com/support/new) and we'll try to support you as best as possible. export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/cli/local-development.mdx b/apps/docs/pages/guides/cli/local-development.mdx index aec434ecea1..f6fe7c6c637 100644 --- a/apps/docs/pages/guides/cli/local-development.mdx +++ b/apps/docs/pages/guides/cli/local-development.mdx @@ -407,12 +407,43 @@ If your project uses Edge Functions, you can deploy these using [`functions depl supabase functions deploy ``` +### Enabling Local Logging + +Local logs rely on the Supabase Analytics Server. This can be enabled via the CLI configuration, and requires a Google Cloud project and BigQuery access. + + +The Google Cloud project must have billing enabled. + + +Requirements: + +1. Google Cloud project number (must be paid) +2. Google Cloud project ID +3. Google Cloud JWT, obtained through [Google Cloud IAM dashboard](https://console.cloud.google.com/iam-admin/iam) + +Once you have these 3 items, follow these steps: + +1. Update your project's `config.toml` and fill in the following: + +``` +[analytics] +enabled = true +gcp_project_number = "123456" +gcp_project_id = "my-project-id" +gcp_jwt_path = "supabase/gcloud.json" +``` + +2. Place your JWT file at the corresponding path and ensure that it is correctly named. + +3. Start your local stack using `supabase start` + +This will switch the logging drivers and will direct logs to the Analytics server. You will be able to view and query your logs via the Studio Logs Explorer and Logs UI. + ## Limitations and considerations The local development environment is not as feature-complete as the Supabase Platform. We're working towards parity between the hosted platform and the local environment. Here are some of the differences: - The Functions interface is coming soon. -- Logs are not supported through the interface, but you can access them through the Docker containers. - You cannot update your project settings in the Dashboard. This must be done using the CLI. export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/realtime/extensions/postgres-changes.mdx b/apps/docs/pages/guides/realtime/extensions/postgres-changes.mdx index f9d2ff2e4d6..aa93a0482cf 100644 --- a/apps/docs/pages/guides/realtime/extensions/postgres-changes.mdx +++ b/apps/docs/pages/guides/realtime/extensions/postgres-changes.mdx @@ -363,6 +363,18 @@ const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY }) ``` +### Refreshed Tokens + +You will need to refresh tokens on your own, but once generated, you can pass them to Realtime. + +For example, if you're using the `supabase-js` `v2` client then you can pass your token like this: + +```js +// Client setup + +supabase.realtime.setAuth('fresh-token') +``` + export const Page = ({ children }) => export default Page diff --git a/apps/docs/pages/guides/self-hosting/analytics/config.mdx b/apps/docs/pages/guides/self-hosting/analytics/config.mdx new file mode 100644 index 00000000000..8267f6a913e --- /dev/null +++ b/apps/docs/pages/guides/self-hosting/analytics/config.mdx @@ -0,0 +1,38 @@ +import Layout from '~/layouts/DefaultGuideLayout' +import { getAnalyticsConfigV0 } from '~/lib/mdx/getConfig' +import Param from '~/components/Params' + +export const spec = getAnalyticsConfigV0() + +export const meta = { + title: 'Analytics Self-hosting Config', + description: 'How to configure and deploy Supabase Analytics.', +} + +
{spec.info.description}
+ +
+{spec.info.tags.map(tag => { + return ( + <> +

{tag.title}

+

{tag.description}

+
+
Parameters
+
    + {spec.parameters.filter(param => param.tags.includes(tag.id)).map((param) => { + return + })} + +
+
+ + ) + +})} + +
+ +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/index.mdx b/apps/docs/pages/index.mdx index d8aebeb4157..f1a68b30401 100644 --- a/apps/docs/pages/index.mdx +++ b/apps/docs/pages/index.mdx @@ -310,6 +310,13 @@ export const apis = [ href: 'reference/self-hosting-realtime/introduction', description: 'Self-host your own Realtime server.', }, + { + title: 'Self-Hosting Analytics', + icon: '/docs/img/icons/menu/reference-analytics', + hasLightIcon: true, + href: 'reference/self-hosting-analytics/introduction', + description: 'Self-host your own Analytics server.', + }, ] export const platform = [ diff --git a/apps/docs/pages/reference/analytics/[...slug].tsx b/apps/docs/pages/reference/analytics/[...slug].tsx new file mode 100644 index 00000000000..3fcf010110f --- /dev/null +++ b/apps/docs/pages/reference/analytics/[...slug].tsx @@ -0,0 +1,167 @@ +import specFile from '~/../../spec/transforms/analytics_v0_openapi_deparsed.json' assert { type: 'json' } +import { gen_v3, enrichedOperation } from '~/lib/refGenerator/helpers' +import { Tabs, CodeBlock } from 'ui' + +import RefSubLayout from '~/layouts/ref/RefSubLayout' + +export type AcceptedValue = { + id: string + name: string + type: 'string' | 'boolean' | 'object' + description?: string +} + +export type Flag = { + id: string + name: string + description: string + default_value: string + accepted_values: AcceptedValue[] +} + +export type ApiParameter = { + example: string + in: string + name: string + required: boolean + schema: { + type: string + example: string + } +} +// @ts-ignore +const generatedSpec = gen_v3(specFile, 'wat', { apiUrl: 'apiv0' }) + +export default function Config() { + return ( + +
+
+
+

{generatedSpec.info.title}

+

{generatedSpec.info.description}

+
+ +
+ {generatedSpec.operations.map((operation: any) => ( +
+ + +
+ + {operation.operation} + {operation.fullPath} + +
+ {/* Path Parameters */} + {operation.parameters && + operation.parameters.filter((parameter) => parameter.in === 'path').length > + 0 && ( +
+

Path parameters

+
    + {operation.parameters && + operation.parameters + .filter((parameter: any) => parameter.in === 'path') + .map((parameter: any) => ( +
  • +
    +
    + {parameter.name} +
    + {parameter.required && ( +
    + REQUIRED +
    + )} +
    +
    +

    {parameter.description}

    +
    + {parameter.example && ( +
    + Example: + + {parameter.example} + +
    + )} +
  • + ))} +
+
+ )} + + {/* Header Parameters */} + {operation.parameters && + operation.parameters.filter((parameter) => parameter.in === 'header').length > + 0 && ( +
+

Header parameters

+
    + {operation.parameters && + operation.parameters + .filter((parameter: any) => parameter.in === 'header') + .map((parameter: any) => ( +
  • +
    + {parameter.name} + + {parameter.required && 'required'} + +
    +
    + Example: + + {parameter.example} + +
    +
  • + ))} +
+
+ )} +
+ + {operation.responseList.length > 0 && ( + <> +

Responses

+ + {operation.responseList.map((response: any) => ( + +

{response.description}

+ {response?.content && response?.content['application/json'] && ( +
+ + {JSON.stringify(response.content['application/json'], null, 2)} + +
+ )} +
+ ))} +
+ + )} +
+
+
+ ))} +
+
+
+
+ ) +} diff --git a/apps/docs/pages/reference/analytics/config.tsx b/apps/docs/pages/reference/analytics/config.tsx new file mode 100644 index 00000000000..e22dfe2e911 --- /dev/null +++ b/apps/docs/pages/reference/analytics/config.tsx @@ -0,0 +1,115 @@ +import fs from 'fs' +import matter from 'gray-matter' +import components from '~/components/index' +import { MDXRemote } from 'next-mdx-remote' +import { serialize } from 'next-mdx-remote/serialize' + +// @ts-expect-error +import specFile from '~/../../spec/analytics_v0_config.yaml' assert { type: 'yml' } +import { Parameter } from '~/lib/refGenerator/refTypes' + +import ReactMarkdown from 'react-markdown' + +// Parameters are grouped on the page by tag +const TAGS = ['general', 'database'] + +export default function Config(props) { + return ( +
+
+
+

{specFile.info.title} Configuration

+ {specFile.info.description} +
+
+ {props.docs + .filter((doc) => doc.introPage) + .map((item) => ( + + ))} +
+ {TAGS.map((tag) => + specFile.parameters + .filter((param: Parameter) => param.tags[0] === tag) + .map((parameter: Parameter, index) => ( +
+ {index === 0 &&

{tag} Settings

} +
+
+

+ $ + {parameter.title} +

+
+
+
+

+ {parameter.description} +

+
+
+ Required: {parameter.required.toString()} +
+
+ Default: + + {parameter.default ? parameter.default.toString() : 'None'} + +
+
+
+ {parameter.links && + parameter.links.map((link) => ( +
+

See also:

+
  • + {link.name} +
  • +
    + ))} +
    +
    +
    +
    +
    + )) + )} +
    +
    +
    +
    + ) +} +export async function getServerSideProps() { + // an array of ids of the intro sections for this library + const introPages = ['analytics'] + + const pages = [...introPages] + + // Grab custom markdown intro page files + const allMarkdownDocs = await Promise.all( + pages.map(async (x: any, i) => { + const pathName = `docs/ref/analytics/${x}.mdx` + + const markdownExists = fs.existsSync(pathName) + + const fileContents = markdownExists ? fs.readFileSync(pathName, 'utf8') : '' + const { data, content } = matter(fileContents) + + return { + id: x, + title: x, + // ...content, + meta: data, + introPage: introPages.includes(x), + content: content ? await serialize(content || '') : null, + } + }) + ) + + return { + props: { + docs: allMarkdownDocs, + }, + } +} diff --git a/apps/docs/pages/reference/self-hosting-analytics/[...slug].tsx b/apps/docs/pages/reference/self-hosting-analytics/[...slug].tsx new file mode 100644 index 00000000000..5f78d5830ff --- /dev/null +++ b/apps/docs/pages/reference/self-hosting-analytics/[...slug].tsx @@ -0,0 +1,24 @@ +import analyticsSpec from '~/../../spec/transforms/analytics_v0_openapi_deparsed.json' assert { type: 'json' } +import selfHostingAnalyticsCommonSections from '~/../../spec/common-self-hosting-analytics-sections.json' +import RefSectionHandler from '~/components/reference/RefSectionHandler' +import { flattenSections } from '~/lib/helpers' +import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' +import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' +import { gen_v3 } from '~/lib/refGenerator/helpers' + +const sections = flattenSections(selfHostingAnalyticsCommonSections) + +// @ts-ignore +const spec = gen_v3(analyticsSpec, 'wat', { apiUrl: 'apiv0' }) + +export default function JSReference(props) { + return +} + +export async function getStaticProps() { + return handleRefStaticProps(sections, '/self-hosting-analytics') +} + +export function getStaticPaths() { + return handleRefGetStaticPaths() +} diff --git a/apps/docs/pages/reference/swift/[...slug].tsx b/apps/docs/pages/reference/swift/[...slug].tsx new file mode 100644 index 00000000000..d0991316c1f --- /dev/null +++ b/apps/docs/pages/reference/swift/[...slug].tsx @@ -0,0 +1,21 @@ +import clientLibsCommonSections from '~/../../spec/common-client-libs-sections.json' +// @ts-expect-error +import spec from '~/../../spec/supabase_swift_v1.yml' assert { type: 'yml' } +import RefSectionHandler from '~/components/reference/RefSectionHandler' +import { flattenSections } from '~/lib/helpers' +import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' +import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' + +const sections = flattenSections(clientLibsCommonSections) + +export default function JSReference(props) { + return +} + +export async function getStaticProps() { + return handleRefStaticProps(sections, '/swift') +} + +export function getStaticPaths() { + return handleRefGetStaticPaths() +} diff --git a/apps/docs/pages/reference/swift/crawlers/[...slug].tsx b/apps/docs/pages/reference/swift/crawlers/[...slug].tsx new file mode 100644 index 00000000000..00d29a772db --- /dev/null +++ b/apps/docs/pages/reference/swift/crawlers/[...slug].tsx @@ -0,0 +1,44 @@ +import clientLibsCommonSections from '~/../../spec/common-client-libs-sections.json' +import typeSpec from '~/../../spec/enrichments/tsdoc_v2/combined.json' +// @ts-expect-error +import spec from '~/../../spec/supabase_swift_v1.yml' assert { type: 'yml' } +import RefSectionHandler from '~/components/reference/RefSectionHandler' +import { flattenSections } from '~/lib/helpers' +import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' +import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' +import { useRouter } from 'next/router' +import RefSEO from '~/components/reference/RefSEO' + +const sections = flattenSections(clientLibsCommonSections) + +export default function JSReference(props) { + const router = useRouter() + const slug = router.query.slug[0] + const filteredSection = sections.filter((section) => section.id === slug) + + const pageTitle = filteredSection[0]?.title + ? `${filteredSection[0]?.title} | Supabase` + : 'Supabase' + + return ( + <> + + + + + ) +} + +export async function getStaticProps() { + return handleRefStaticProps(sections, '/swift') +} + +export function getStaticPaths() { + return handleRefGetStaticPaths() +} diff --git a/apps/docs/pages/reference/swift/v1/[...slug].tsx b/apps/docs/pages/reference/swift/v1/[...slug].tsx new file mode 100644 index 00000000000..1ffdebde8c3 --- /dev/null +++ b/apps/docs/pages/reference/swift/v1/[...slug].tsx @@ -0,0 +1,21 @@ +import clientLibsCommonSections from '~/../../spec/common-client-libs-sections.json' +// @ts-expect-error +import spec from '~/../../spec/supabase_swift_v1.yml' assert { type: 'yml' } +import RefSectionHandler from '~/components/reference/RefSectionHandler' +import { flattenSections } from '~/lib/helpers' +import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' +import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' + +const sections = flattenSections(clientLibsCommonSections) + +export default function JSReference(props) { + return +} + +export async function getStaticProps() { + return handleRefStaticProps(sections, '/swift/v1') +} + +export function getStaticPaths() { + return handleRefGetStaticPaths() +} diff --git a/apps/docs/pages/reference/swift/v1/crawlers/[...slug].tsx b/apps/docs/pages/reference/swift/v1/crawlers/[...slug].tsx new file mode 100644 index 00000000000..13ae4435ddd --- /dev/null +++ b/apps/docs/pages/reference/swift/v1/crawlers/[...slug].tsx @@ -0,0 +1,43 @@ +import clientLibsCommonSections from '~/../../spec/common-client-libs-sections.json' +import typeSpec from '~/../../spec/enrichments/tsdoc_v2/combined.json' +// @ts-expect-error +import spec from '~/../../spec/supabase_swift_v1.yml' assert { type: 'yml' } +import RefSectionHandler from '~/components/reference/RefSectionHandler' +import { flattenSections } from '~/lib/helpers' +import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' +import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' +import { useRouter } from 'next/router' +import RefSEO from '~/components/reference/RefSEO' + +const sections = flattenSections(clientLibsCommonSections) + +export default function JSReference(props) { + const router = useRouter() + const slug = router.query.slug[0] + const filteredSection = sections.filter((section) => section.id === slug) + + const pageTitle = filteredSection[0]?.title + ? `${filteredSection[0]?.title} | Supabase` + : 'Supabase' + return ( + <> + + + + + ) +} + +export async function getStaticProps() { + return handleRefStaticProps(sections, '/swift/v1') +} + +export function getStaticPaths() { + return handleRefGetStaticPaths() +} diff --git a/apps/docs/public/img/icons/menu/analytics-light.svg b/apps/docs/public/img/icons/menu/analytics-light.svg new file mode 100644 index 00000000000..3cea218cd72 --- /dev/null +++ b/apps/docs/public/img/icons/menu/analytics-light.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/docs/public/img/icons/menu/analytics.svg b/apps/docs/public/img/icons/menu/analytics.svg new file mode 100644 index 00000000000..2c3be34319e --- /dev/null +++ b/apps/docs/public/img/icons/menu/analytics.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/docs/public/img/icons/menu/reference-analytics-light.svg b/apps/docs/public/img/icons/menu/reference-analytics-light.svg new file mode 100644 index 00000000000..840fb813f92 --- /dev/null +++ b/apps/docs/public/img/icons/menu/reference-analytics-light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/docs/public/img/icons/menu/reference-analytics.svg b/apps/docs/public/img/icons/menu/reference-analytics.svg new file mode 100644 index 00000000000..840fb813f92 --- /dev/null +++ b/apps/docs/public/img/icons/menu/reference-analytics.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/docs/public/img/icons/menu/reference-swift-light.svg b/apps/docs/public/img/icons/menu/reference-swift-light.svg new file mode 100644 index 00000000000..d11ab08bb58 --- /dev/null +++ b/apps/docs/public/img/icons/menu/reference-swift-light.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/docs/public/img/icons/menu/reference-swift.svg b/apps/docs/public/img/icons/menu/reference-swift.svg new file mode 100644 index 00000000000..d11ab08bb58 --- /dev/null +++ b/apps/docs/public/img/icons/menu/reference-swift.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/docs/public/img/icons/menu/swift.svg b/apps/docs/public/img/icons/menu/swift.svg new file mode 100644 index 00000000000..d11ab08bb58 --- /dev/null +++ b/apps/docs/public/img/icons/menu/swift.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/docs/public/img/icons/swift-icon.svg b/apps/docs/public/img/icons/swift-icon.svg new file mode 100644 index 00000000000..d11ab08bb58 --- /dev/null +++ b/apps/docs/public/img/icons/swift-icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/www/_blog/2023-04-04-infinite-scroll-with-nextjs-framer-motion.mdx b/apps/www/_blog/2023-04-04-infinite-scroll-with-nextjs-framer-motion.mdx new file mode 100644 index 00000000000..406cc058b93 --- /dev/null +++ b/apps/www/_blog/2023-04-04-infinite-scroll-with-nextjs-framer-motion.mdx @@ -0,0 +1,337 @@ +--- +title: Infinite scroll with Next.js, Framer Motion, and Supabase +description: Lazy load and paginate data on scroll with Next.js and a sprinkle of Framer Motion magic ✨ +author: fsansalvadore +image: infinite-scroll-thumb.jpg +thumb: infinite-scroll-thumb.jpg +tags: + - tutorial + - launch-week + - Next-js +date: '2023-04-04' +toc_depth: 6 +--- + +Every Launch Week, we strive to approach things differently as part of our Kaizen mindset. For [Launch Week 7](https://www.supabase.com/launch-week), we decided to add some twists to our popular ticket system. While we made several changes, this post will focus on one specific modification (we'll share more about the rest this week!). + +Our goal was to feature all the amazing people who generated tickets on a [single page](https://supabase.com/launch-week/tickets). However, displaying them all at once could take a toll on the loading time and negatively affect the user experience. That's where lazy loading smaller subsets via infinite scrolling came in as the solution. + +With infinite scrolling, users can effortlessly scroll through content while only loading the necessary data as they go. This not only enhances performance but also creates a seamless and enjoyable user experience. + +In this post, we'll detail our process of implementing infinite scrolling using Next.js, Framer Motion, and Supabase. + +## Step 0. Load the first batch + +First things first, let's install the dependencies we'll need. + +```bash +npm install @supabase/supabase-js lodash framer-motion +``` + +Set up the supabase-js client and fetch the first 20 tickets through `getServerSideProps` so we don't start with an empty screen. + +We'll assume our table in the db is called `my_tickets_table`. + +```jsx +import { useEffect, useState } from 'react' +import { createClient } from '@supabase/supabase-js' + +const supabase = createClient('supabase-url', 'supabase-key') + +export default function TicketsPage({ tickets }) { + const [loadedTickets, setLoadedTickets] = useState(tickets) + + return ( +
    + {loadedTickets.map((ticket, index) => ( + {/* We'll get to this part later */} + ))} +
    + ) + +export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { + const { data: tickets } = await supabase! + .from('my_tickets_table') + .select('*') + .order('createdAt', { ascending: false }) + .limit(20) + + return { + props: { + tickets, + }, + } +} +``` + +## Step 1. Listen to the scroll + +To detect if the user is scrolling we can listen to the window `scroll` event. + +If you try to `console.log()` the event returned in the listener callback, you'll notice that it fires on every single pixel scrolled. We want to avoid triggering like crazy, so we'll use a [lodash debounce function](https://www.geeksforgeeks.org/lodash-_-debounce-method/) to limit how often we call this event to once every 200 milliseconds. + +Here's what it looks like: + +```jsx +import { useEffect } from 'react' +import { debounce } from 'lodash' + +const handleScroll = () => { + // Do stuff when scrolling +} + +useEffect(() => { + const handleDebouncedScroll = debounce(() => handleScroll(), 200) + window.addEventListener('scroll', handleDebouncedScroll) + return () => { + window.removeEventListener('scroll', handleDebouncedScroll) + } +}, []) +``` + +## Step 2. Check if the container intersects the viewport + +Next, we want to check if the bottom of the tickets container intersects with the bottom of the viewport. We can use the `getBoundingClientRect` method to get the position of the container and then compare it with the height of the viewport. + +```jsx +import { useRef, useState } from 'react' + +// ... + +const containerRef = useRef(null) +const [offset, setOffset] = useState(1) +const [isInView, setIsInView] = useState(false) + +const handleScroll = (container) => { + if (containerRef.current && typeof window !== 'undefined') { + const container = containerRef.current + const { bottom } = container.getBoundingClientRect() + const { innerHeight } = window + setIsInView((prev) => bottom <= innerHeight) + } +} + +useEffect(() => { + if (isInView) { + loadMoreUsers(offset) + } +}, [isInView]) + +return
    {/* List of loaded tickets */}
    +``` + +## Step 3. Load tickets based on the offset + +Now we can load more tickets based on the current offset. We'll use the [range](https://supabase.com/docs/reference/javascript/range) method from the supabase-js library to easily work with the pagination logic. + +```jsx +export default function TicketsPage() { + const PAGE_COUNT = 20 + const [offset, setOffset] = useState(1) + const [isLoading, setIsLoading] = useState(false) + const [isInView, setIsInView] = useState(false) + + useEffect(() => { + if (isInView) { + loadMoreTickets(offset) + } + }, [isInView]) + + const loadMoreTickets = async (offset: number) => { + setIsLoading(true) + // Every time we fetch, we want to increase + // the offset to load fresh tickets + setOffset((prev) => prev + 1) + const { data: newTickets } = await fetchTickets(offset, PAGE_COUNT) + // Merge new tickets with all previously loaded + setLoadedTickets((prevTickets) => [...prevTickets, ...newTickets]) + setIsLoading(false) + } + + const fetchTickets = async (offset, limit) => { + const from = offset * PAGE_COUNT + const to = from + PAGE_COUNT - 1 + + const { data } = await supabase! + .from('my_tickets_table') + .select('*') + .range(from, to) + .order('createdAt', { ascending: false }) + + + return data + } +} +``` + +## Step 4. Animate the tickets + +Now that we have our tickets loaded, we want to add some animation to make them pop like dominos as they appear on the screen. For this, we're going to use the [Framer Motion](https://www.framer.com/motion/) library. + +We'll wrap each ticket in a motion component and add a transition effect to stagger their appearance on the screen: + +```jsx +import { motion } from 'framer-motion' + +// ... + +{ + loadedTickets.map((ticket, index) => { + // each ticket will be delayed based on it's index + // but we need to subtract the delay from all the previously loaded tickets + const recalculatedDelay = i >= PAGE_COUNT * 2 ? (i - PAGE_COUNT * (offset - 1)) / 15 : i / 15 + + return ( + + {/* Actual ticket component */} + + ) + }) +} +``` + +With this code, each ticket will start with an opacity of 0 and a y position of 20. As it animates into view, it will fade in and move up to its final position. The delay for each ticket will be based on its index in the array, creating a nice staggered effect. + +## Step 5. Stop listening when finished + +Once all the tickets have been loaded, we want to stop listening to the scroll event to avoid unnecessary requests. We can do this by setting a state variable called `isLast` to true whenever the length of the response will be less than `PAGE_COUNT`: + +```jsx +if (newTickets.length < PAGE_COUNT) { + setIsLast(true) +} +``` + +We'll use this code to conditionally remove the event listener: + +```jsx +useEffect(() => { + const handleDebouncedScroll = debounce(() => !isLast && handleScroll(), 200) + window.addEventListener('scroll', handleScroll) + return () => { + window.removeEventListener('scroll', handleScroll) + } +}, []) +``` + +Now, when the `isLast` state variable is true, the event listener will be removed and the component will stop listening to the scroll event. + +## Wrap up + +That's it! We hope this post enabled you to build the next awesome infinite scroll. + +Here's the complete code: + +```jsx +import { useEffect, useState, useRef } from 'react' +import { createClient } from '@supabase/supabase-js' +import { debounce } from 'lodash' +import { motion } from 'framer-motion' + +const supabase = createClient('supabase-url', 'supabase-key') + +export default function TicketsPage({ tickets }) { + const PAGE_COUNT = 20 + const containerRef = useRef(null) + const [loadedTickets, setLoadedTickets] = useState(tickets) + const [offset, setOffset] = useState(1) + const [isLoading, setIsLoading] = useState(false) + const [isInView, setIsInView] = useState(false) + + const handleScroll = (container) => { + if (containerRef.current && typeof window !== 'undefined') { + const container = containerRef.current + const { bottom } = container.getBoundingClientRect() + const { innerHeight } = window + setIsInView((prev) => bottom <= innerHeight) + } + } + + useEffect(() => { + const handleDebouncedScroll = debounce(() => !isLast && handleScroll(), 200) + window.addEventListener('scroll', handleScroll) + return () => { + window.removeEventListener('scroll', handleScroll) + } + }, []) + + useEffect(() => { + if (isInView) { + loadMoreTickets(offset) + } + }, [isInView]) + + const loadMoreTickets = async (offset: number) => { + setIsLoading(true) + setOffset((prev) => prev + 1) + const { data: newTickets } = await fetchTickets(offset, PAGE_COUNT) + setLoadedTickets((prevTickets) => [...prevTickets, ...newTickets]) + setIsLoading(false) + } + + const fetchTickets = async (offset) => { + const from = offset * PAGE_COUNT + const to = from + PAGE_COUNT - 1 + + const { data } = await supabase! + .from('my_tickets_table') + .select('*') + .range(from, to) + .order('createdAt', { ascending: false }) + + return data + } + + return ( +
    + { + loadedTickets.map((ticket, index) => { + const recalculatedDelay = + i >= PAGE_COUNT * 2 ? (i - PAGE_COUNT * (offset - 1)) / 15 : i / 15 + + return ( + + {/* Actual ticket component */} + + ) + }) + } +
    + ) + +export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { + const { data: tickets } = await supabase! + .from('my_tickets_table') + .select('*') + .order('createdAt', { ascending: false }) + .limit(20) + + return { + props: { + tickets, + }, + } +} +``` + +If you also want to feature in the endless tickets page and have a chance to win cool swag, you're invited to [generate your unique ticket](https://www.supabase.com/launch-week) until April 16th. + +See you at Launch Week! 👋 diff --git a/apps/www/components/LaunchWeek/Banners/CountdownBanner.tsx b/apps/www/components/LaunchWeek/Banners/CountdownBanner.tsx new file mode 100644 index 00000000000..12fe5c3738c --- /dev/null +++ b/apps/www/components/LaunchWeek/Banners/CountdownBanner.tsx @@ -0,0 +1,85 @@ +import React from 'react' +import Link from 'next/link' +import Countdown from 'react-countdown' +import _announcement from '~/data/Announcement.json' +import { AnnouncementProps } from '../../Nav/Announcement' +import { useRouter } from 'next/router' + +interface CountdownStepProps { + value: string | number + unit: string +} + +const announcement = _announcement as AnnouncementProps + +function CountdownStep({ value, unit }: CountdownStepProps) { + return ( +
    +
    + {value} + {unit} +
    +
    + ) +} + +function CountdownBanner() { + const { pathname } = useRouter() + const isLaunchWeekPage = pathname === '/launch-week' + const isLaunchWeekSection = pathname.includes('launch-week') + + const renderer = ({ days, hours, minutes, seconds, completed }: any) => { + if (completed) { + // Render a completed state + return ( +
    +

    Supabase Launch Week 7

    + +
    + ) + } else { + // Render countdown + return ( +
    +

    + Supabase Launch Week 7 +

    +
    + : + : + : + +
    + {!isLaunchWeekPage && ( + + )} +
    + ) + } + } + + return ( +
    + +
    + ) +} + +export default CountdownBanner diff --git a/apps/www/components/LaunchWeek/Ticket/ActualTicket.tsx b/apps/www/components/LaunchWeek/Ticket/ActualTicket.tsx index 1fe88229da0..e687a263c48 100644 --- a/apps/www/components/LaunchWeek/Ticket/ActualTicket.tsx +++ b/apps/www/components/LaunchWeek/Ticket/ActualTicket.tsx @@ -71,7 +71,7 @@ export default function Ticket({ /> {username && (
    - +
    )} diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx index 8ac0074d341..c228e815a67 100644 --- a/apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx +++ b/apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx @@ -6,16 +6,17 @@ import { IconCopy, IconCheck } from 'ui' type Props = { username: string + isGolden?: boolean } -export default function TicketCopy({ username }: Props) { +export default function TicketCopy({ username, isGolden }: Props) { const [fadeOpacity, setFadeOpacity] = useState(1) const [scrolling, setScrolling] = useState(false) const [copyEnabled, setCopyEnabled] = useState(false) const [copied, setCopied] = useState(false) const scrollRef = useRef(null) const buttonRef = useRef(null) - const url = `${SITE_URL}/tickets/${username}?lw=7` + const url = `${SITE_URL}/tickets/${username}?lw=7${isGolden ? `&golden=true` : ''}` useEffect(() => { if (navigator.clipboard) { setCopyEnabled(true) diff --git a/apps/www/components/Nav/Announcement.tsx b/apps/www/components/Nav/Announcement.tsx index a85d9c524f2..60595a309be 100644 --- a/apps/www/components/Nav/Announcement.tsx +++ b/apps/www/components/Nav/Announcement.tsx @@ -1,26 +1,37 @@ import React, { useEffect, useState } from 'react' import _announcement from '~/data/Announcement.json' -import { IconChevronRight, IconX } from 'ui' +import { IconX } from 'ui' import { useRouter } from 'next/router' +import { PropsWithChildren } from 'react' -interface AnnouncementProps { +export interface AnnouncementProps { show: boolean text: string - cta: string + launchDate: string link: string badge?: string } const announcement = _announcement as AnnouncementProps -const Announcement = () => { +interface AnnouncementComponentProps { + show?: boolean + className?: string +} + +const Announcement = ({ + show = true, + className, + children, +}: PropsWithChildren) => { const [hidden, setHidden] = useState(true) const router = useRouter() + const isLaunchWeekSection = router.pathname.includes('launch-week') // override to hide announcement - if (!announcement.show) return null + if (!show || !announcement.show) return null // construct the key for the announcement, based on the title text const announcementKey = 'announcement_' + announcement.text.replace(/ /g, '') @@ -42,50 +53,23 @@ const Announcement = () => { function handleLink() { router.push(announcement.link) - window.localStorage.setItem(announcementKey, 'hidden') } - if (hidden) { + // Always show if on LW section + if (!isLaunchWeekSection && hidden) { return null } else { return ( -
    -
    - {announcement.text} - - {announcement.badge && ( -
    - {announcement.badge} -
    - )} -
    - - {announcement.cta} - -
    -
    - -
    +
    + {!isLaunchWeekSection && ( +
    + +
    + )} + {children}
    ) } diff --git a/apps/www/components/Nav/index.tsx b/apps/www/components/Nav/index.tsx index 17e7eeac174..7baf842c5f5 100644 --- a/apps/www/components/Nav/index.tsx +++ b/apps/www/components/Nav/index.tsx @@ -17,6 +17,7 @@ import TextLink from '../TextLink' import Image from 'next/image' import * as supabaseLogoWordmarkDark from 'common/assets/images/supabase-logo-wordmark--dark.png' import * as supabaseLogoWordmarkLight from 'common/assets/images/supabase-logo-wordmark--light.png' +import CountdownBanner from '../LaunchWeek/Banners/CountdownBanner' const Nav = () => { const { isDarkMode } = useTheme() @@ -194,7 +195,9 @@ const Nav = () => { return ( <> - {/* */} + + +
    {
    - {/*
    */}
    {/*
    */} {/* Mobile Nav Menu */} diff --git a/apps/www/data/Announcement.json b/apps/www/data/Announcement.json index a0ab4c9ff86..ee460e86b28 100644 --- a/apps/www/data/Announcement.json +++ b/apps/www/data/Announcement.json @@ -1,7 +1,7 @@ { "show": true, - "text": "Launch Week 6 begins December 12th", - "cta": "Don't miss a thing, get your free ticket", - "link": "https://supabase.com/launch-week", - "badge": "Get a reminder" + "text": "Supabase Launch Week 7", + "launchDate": "2023-04-10T07:00:00.000-07:00", + "link": "/launch-week", + "badge": "Get your ticket" } diff --git a/apps/www/lib/authors.json b/apps/www/lib/authors.json index e421f0bc24e..00e1b9119a7 100644 --- a/apps/www/lib/authors.json +++ b/apps/www/lib/authors.json @@ -271,5 +271,12 @@ "position": "Open Source Contributor", "author_url": "https://github.com/gregnr", "author_image_url": "https://github.com/gregnr.png" + }, + { + "author_id": "fsansalvadore", + "author": "Francesco Sansalvadore", + "position": "Engineering", + "author_url": "https://github.com/fsansalvadore", + "author_image_url": "https://github.com/fsansalvadore.png" } ] diff --git a/apps/www/package.json b/apps/www/package.json index e34b14c8ab1..386cf50b53a 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -41,6 +41,7 @@ "parse-numeric-range": "^1.3.0", "react": "^17.0.2", "react-copy-to-clipboard": "^5.0.2", + "react-countdown": "^2.3.5", "react-dom": "^17.0.2", "react-ga": "^3.3.0", "react-markdown": "^8.0.3", diff --git a/apps/www/pages/launch-week/index.tsx b/apps/www/pages/launch-week/index.tsx index d882ec9625c..4fea6ced64b 100644 --- a/apps/www/pages/launch-week/index.tsx +++ b/apps/www/pages/launch-week/index.tsx @@ -95,7 +95,7 @@ export default function TicketHome({ users }: Props) { }} /> -
    +
    diff --git a/apps/www/pages/launch-week/tickets/[username].tsx b/apps/www/pages/launch-week/tickets/[username].tsx index 7c2707ddf5d..4f711ec921d 100644 --- a/apps/www/pages/launch-week/tickets/[username].tsx +++ b/apps/www/pages/launch-week/tickets/[username].tsx @@ -64,7 +64,7 @@ export default function UsernamePage({ user, users, ogImageUrl }: Props) { }} /> -
    +
    diff --git a/apps/www/pages/launch-week/tickets/index.tsx b/apps/www/pages/launch-week/tickets/index.tsx index 05c1bdb31df..e916dc8c0c5 100644 --- a/apps/www/pages/launch-week/tickets/index.tsx +++ b/apps/www/pages/launch-week/tickets/index.tsx @@ -52,21 +52,12 @@ export default function TicketsPage({ users }: Props) { loadMoreUsers(offset) }, [hasLoaded]) - const getPagination = (page: number = 1, size: number) => { - const limit = size ? +size : 3 - const from = page ? page * limit : 0 - const to = page ? from + size : size - - return { from, to } - } - - const loadUsers = async (offset = 0, pageCount: number) => { - const { from, to } = getPagination(offset, pageCount) - + const loadUsers = async (offset: number) => { + const from = offset * PAGE_COUNT return await supabaseAdmin! .from('lw7_tickets_golden') .select('*') - .range(from, to - 1) + .range(from, from + PAGE_COUNT - 1) .order('createdAt', { ascending: false }) } @@ -74,7 +65,7 @@ export default function TicketsPage({ users }: Props) { if (isLast) return setIsLoading(true) setOffset((prev) => prev + 1) - const { data: users } = await loadUsers(offset, PAGE_COUNT) + const { data: users } = await loadUsers(offset) setLoadedUsers((prevUsers) => [...prevUsers, ...(users as any[])]) if ((users as any[]).length < PAGE_COUNT) setIsLast(true) setIsLoading(false) @@ -84,8 +75,8 @@ export default function TicketsPage({ users }: Props) { document.body.className = 'dark bg-[#1C1C1C]' }, []) - const isBottomInView = () => { - if (ref.current && window) { + const handleScroll = () => { + if (ref.current && typeof window !== 'undefined') { const rect = (ref.current as HTMLDivElement)?.getBoundingClientRect() const isInView = rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) setHasLoaded((prev) => !prev && isInView) @@ -93,13 +84,12 @@ export default function TicketsPage({ users }: Props) { } useEffect(() => { - window.addEventListener( - 'scroll', - debounce(() => !isLast && isBottomInView(), 200) - ) + const handleDebouncedScroll = debounce(() => !isLast && handleScroll(), 200) + + window.addEventListener('scroll', handleDebouncedScroll) return () => { - window.removeEventListener('scroll', isBottomInView) + window.removeEventListener('scroll', handleDebouncedScroll) } }, []) @@ -119,7 +109,7 @@ export default function TicketsPage({ users }: Props) { }} /> -
    +
    diff --git a/apps/www/public/images/blog/infinite-scroll-thumb.jpg b/apps/www/public/images/blog/infinite-scroll-thumb.jpg new file mode 100644 index 00000000000..51c58ad84cb Binary files /dev/null and b/apps/www/public/images/blog/infinite-scroll-thumb.jpg differ diff --git a/apps/www/public/planetpg-angelico_de_los_reyes-rss.xml b/apps/www/public/planetpg-angelico_de_los_reyes-rss.xml index 84fe8fab257..7bd6ba6d8c6 100644 --- a/apps/www/public/planetpg-angelico_de_los_reyes-rss.xml +++ b/apps/www/public/planetpg-angelico_de_los_reyes-rss.xml @@ -5,35 +5,35 @@ https://supabase.com/blog Latest Postgres news from Angelico de los Reyes at Supabase en - Thu, 15 Dec 2022 03:03:30 +0000 + Thu, 15 Dec 2022 23:11:00 +0000 https://supabase.com/blog/postgres-point-in-time-recovery Point in Time Recovery is now available for Pro projects https://supabase.com/blog/postgres-point-in-time-recovery We're making PITR available for more projects, with a new Dashboard UI that makes it simple to use. - Thu, 15 Dec 2022 03:03:30 +0000 + Thu, 15 Dec 2022 23:11:00 +0000 https://supabase.com/blog/continuous-postgresql-backup-walg Continuous PostgreSQL Backups using WAL-G https://supabase.com/blog/continuous-postgresql-backup-walg Have you ever wanted to restore your database's state to a particular moment in time? This post explains how, using WAL-G. - Sun, 02 Aug 2020 02:02:30 +0000 + Sat, 01 Aug 2020 22:10:00 +0000 https://supabase.com/blog/postgresql-templates What are PostgreSQL Templates? https://supabase.com/blog/postgresql-templates What are PostgreSQL templates and what are they used for? - Thu, 09 Jul 2020 02:02:30 +0000 + Wed, 08 Jul 2020 22:10:00 +0000 https://supabase.com/blog/postgresql-physical-logical-backups Physical vs Logical Backups in PostgreSQL https://supabase.com/blog/postgresql-physical-logical-backups What are physical and logical backups in Postgres? - Tue, 07 Jul 2020 02:02:30 +0000 + Mon, 06 Jul 2020 22:10:00 +0000 diff --git a/apps/www/public/planetpg-ant_wilson-rss.xml b/apps/www/public/planetpg-ant_wilson-rss.xml index 07cfe4056b8..43868faddee 100644 --- a/apps/www/public/planetpg-ant_wilson-rss.xml +++ b/apps/www/public/planetpg-ant_wilson-rss.xml @@ -5,14 +5,14 @@ https://supabase.com/blog Latest Postgres news from Ant Wilson at Supabase en - Sat, 27 Feb 2021 03:03:30 +0000 + Fri, 26 Feb 2021 23:11:00 +0000 https://supabase.com/blog/cracking-postgres-interview Cracking PostgreSQL Interview Questions https://supabase.com/blog/cracking-postgres-interview Understand the top PostgreSQL Interview Questions - Sat, 27 Feb 2021 03:03:30 +0000 + Fri, 26 Feb 2021 23:11:00 +0000 diff --git a/apps/www/public/planetpg-bo_lu-rss.xml b/apps/www/public/planetpg-bo_lu-rss.xml index 16f75abaa7e..9dbe70f0c1a 100644 --- a/apps/www/public/planetpg-bo_lu-rss.xml +++ b/apps/www/public/planetpg-bo_lu-rss.xml @@ -5,14 +5,14 @@ https://supabase.com/blog Latest Postgres news from Bo Lu at Supabase en - Wed, 14 Dec 2022 03:03:30 +0000 + Wed, 14 Dec 2022 23:11:00 +0000 https://supabase.com/blog/postgres-foreign-data-wrappers-rust Supabase Wrappers, a Postgres FDW framework written in Rust https://supabase.com/blog/postgres-foreign-data-wrappers-rust A framework for building Postgres Foreign Data Wrappers which connects to Stripe, Firebase, Clickhouse, and more. - Wed, 14 Dec 2022 03:03:30 +0000 + Wed, 14 Dec 2022 23:11:00 +0000 diff --git a/apps/www/public/planetpg-burggraf-rss.xml b/apps/www/public/planetpg-burggraf-rss.xml index 26233a86f1e..2efaf1c4523 100644 --- a/apps/www/public/planetpg-burggraf-rss.xml +++ b/apps/www/public/planetpg-burggraf-rss.xml @@ -5,21 +5,21 @@ https://supabase.com/blog Latest Postgres news from Mark Burggraf at Supabase en - Wed, 23 Nov 2022 03:03:30 +0000 + Wed, 23 Nov 2022 23:11:00 +0000 https://supabase.com/blog/sql-or-nosql-both-with-postgresql SQL or NoSQL? Why not use both (with PostgreSQL)? https://supabase.com/blog/sql-or-nosql-both-with-postgresql How to turn Postgres into an easy-to-use NoSQL database that retains all the power of SQL - Wed, 23 Nov 2022 03:03:30 +0000 + Wed, 23 Nov 2022 23:11:00 +0000 https://supabase.com/blog/postgres-wasm Postgres WASM by Snaplet and Supabase https://supabase.com/blog/postgres-wasm We're open sourcing postgres-wasm, a PostgresQL server that runs inside a browser, with our friends at Snaplet. - Sun, 02 Oct 2022 02:02:30 +0000 + Sun, 02 Oct 2022 22:10:00 +0000 diff --git a/apps/www/public/planetpg-michel-rss.xml b/apps/www/public/planetpg-michel-rss.xml index 3bcd14a76b8..5ad62ff8606 100644 --- a/apps/www/public/planetpg-michel-rss.xml +++ b/apps/www/public/planetpg-michel-rss.xml @@ -5,21 +5,21 @@ https://supabase.com/blog Latest Postgres news from Michel Pelletier at Supabase en - Thu, 15 Dec 2022 03:03:30 +0000 + Thu, 15 Dec 2022 23:11:00 +0000 https://supabase.com/blog/vault-now-in-beta Supabase Vault is now in Beta https://supabase.com/blog/vault-now-in-beta A Postgres extension to store encrypted secrets and encrypt data. - Thu, 15 Dec 2022 03:03:30 +0000 + Thu, 15 Dec 2022 23:11:00 +0000 https://supabase.com/blog/transparent-column-encryption-with-postgres Transparent Column Encryption with Postgres https://supabase.com/blog/transparent-column-encryption-with-postgres Using pgsodium's Transparent Column Encryption to encrypt data and provide your users with row-level encryption. - Wed, 30 Nov 2022 03:03:30 +0000 + Wed, 30 Nov 2022 23:11:00 +0000 diff --git a/apps/www/public/planetpg-oli_rice-rss.xml b/apps/www/public/planetpg-oli_rice-rss.xml index 286dd45d87e..19ac784cc0c 100644 --- a/apps/www/public/planetpg-oli_rice-rss.xml +++ b/apps/www/public/planetpg-oli_rice-rss.xml @@ -5,42 +5,42 @@ https://supabase.com/blog Latest Postgres news from Oliver Rice at Supabase en - Thu, 16 Feb 2023 03:03:30 +0000 + Thu, 16 Feb 2023 23:11:00 +0000 https://supabase.com/blog/type-constraints-in-65-lines-of-sql Type Constraints in 65 lines of SQL https://supabase.com/blog/type-constraints-in-65-lines-of-sql Creating validated data types in Postgres - Thu, 16 Feb 2023 03:03:30 +0000 + Thu, 16 Feb 2023 23:11:00 +0000 https://supabase.com/blog/pg-graphql-v1 pg_graphql v1.0 https://supabase.com/blog/pg-graphql-v1 Announcing the v1.0 release of pg_graphql - Thu, 15 Dec 2022 03:03:30 +0000 + Thu, 15 Dec 2022 23:11:00 +0000 https://supabase.com/blog/postgres-foreign-data-wrappers-rust Supabase Wrappers, a Postgres FDW framework written in Rust https://supabase.com/blog/postgres-foreign-data-wrappers-rust A framework for building Postgres Foreign Data Wrappers which connects to Stripe, Firebase, Clickhouse, and more. - Wed, 14 Dec 2022 03:03:30 +0000 + Wed, 14 Dec 2022 23:11:00 +0000 https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation pg_jsonschema: JSON Schema support for Postgres https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation Today we're releasing pg_jsonschema, a Postgres extension for JSON validation. - Thu, 18 Aug 2022 02:02:30 +0000 + Thu, 18 Aug 2022 22:10:00 +0000 https://supabase.com/blog/audit Postgres Auditing in 150 lines of SQL https://supabase.com/blog/audit PostgreSQL has a robust set of features which we can leverage to create a generic auditing solution in 150 lines of SQL. - Mon, 07 Mar 2022 03:03:30 +0000 + Mon, 07 Mar 2022 23:11:00 +0000 diff --git a/apps/www/public/planetpg-paul_copplestone-rss.xml b/apps/www/public/planetpg-paul_copplestone-rss.xml index 3bee1dc1f0d..707f2e1f06e 100644 --- a/apps/www/public/planetpg-paul_copplestone-rss.xml +++ b/apps/www/public/planetpg-paul_copplestone-rss.xml @@ -5,42 +5,42 @@ https://supabase.com/blog Latest Postgres news from Paul Copplestone at Supabase en - Wed, 14 Dec 2022 03:03:30 +0000 + Wed, 14 Dec 2022 23:11:00 +0000 https://supabase.com/blog/postgres-foreign-data-wrappers-rust Supabase Wrappers, a Postgres FDW framework written in Rust https://supabase.com/blog/postgres-foreign-data-wrappers-rust A framework for building Postgres Foreign Data Wrappers which connects to Stripe, Firebase, Clickhouse, and more. - Wed, 14 Dec 2022 03:03:30 +0000 + Wed, 14 Dec 2022 23:11:00 +0000 https://supabase.com/blog/postgres-crdt pg_crdt - an experimental CRDT extension for Postgres https://supabase.com/blog/postgres-crdt Embedding Yjs and Automerge into Postgres for collaborative applications. - Fri, 09 Dec 2022 03:03:30 +0000 + Fri, 09 Dec 2022 23:11:00 +0000 https://supabase.com/blog/partial-postgresql-data-dumps-with-rls Partial data dumps using Postgres Row Level Security https://supabase.com/blog/partial-postgresql-data-dumps-with-rls Using RLS to create seed files for local PostgreSQL testing. - Mon, 27 Jun 2022 02:02:30 +0000 + Mon, 27 Jun 2022 22:10:00 +0000 https://supabase.com/blog/postgres-as-a-cron-server Postgres as a CRON Server https://supabase.com/blog/postgres-as-a-cron-server Running repetitive tasks with your Postgres database. - Fri, 05 Mar 2021 03:03:30 +0000 + Thu, 04 Mar 2021 23:11:00 +0000 https://supabase.com/blog/postgresql-views Postgres Views https://supabase.com/blog/postgresql-views Creating and using a view in PostgreSQL. - Wed, 18 Nov 2020 03:03:30 +0000 + Tue, 17 Nov 2020 23:11:00 +0000 diff --git a/apps/www/public/planetpg-pavel-rss.xml b/apps/www/public/planetpg-pavel-rss.xml index e6dbbb4c1fe..b2852591d5a 100644 --- a/apps/www/public/planetpg-pavel-rss.xml +++ b/apps/www/public/planetpg-pavel-rss.xml @@ -5,21 +5,21 @@ https://supabase.com/blog Latest Postgres news from Pavel Borisov at Supabase en - Thu, 15 Dec 2022 03:03:30 +0000 + Thu, 15 Dec 2022 23:11:00 +0000 https://supabase.com/blog/new-in-postgres-15 What's new in Postgres 15? https://supabase.com/blog/new-in-postgres-15 Describes the release of Postgres 15, new features and reasons to use it - Thu, 15 Dec 2022 03:03:30 +0000 + Thu, 15 Dec 2022 23:11:00 +0000 https://supabase.com/blog/postgresql-commitfest What is PostgreSQL commitfest and how to contribute https://supabase.com/blog/postgresql-commitfest A time-tested method for contributing to the core Postgres code - Wed, 26 Oct 2022 02:02:30 +0000 + Wed, 26 Oct 2022 22:10:00 +0000 diff --git a/apps/www/public/planetpg-steve_chavez-rss.xml b/apps/www/public/planetpg-steve_chavez-rss.xml index 575cafb6b06..d8077fd31f5 100644 --- a/apps/www/public/planetpg-steve_chavez-rss.xml +++ b/apps/www/public/planetpg-steve_chavez-rss.xml @@ -5,21 +5,21 @@ https://supabase.com/blog Latest Postgres news from Steve Chavez at Supabase en - Thu, 15 Dec 2022 03:03:30 +0000 + Thu, 15 Dec 2022 23:11:00 +0000 https://supabase.com/blog/postgrest-11-prerelease PostgREST 11 pre-release https://supabase.com/blog/postgrest-11-prerelease Describes new features of PostgREST 11 pre-release - Thu, 15 Dec 2022 03:03:30 +0000 + Thu, 15 Dec 2022 23:11:00 +0000 https://supabase.com/blog/roles-postgres-hooks Protecting reserved roles with PostgreSQL Hooks https://supabase.com/blog/roles-postgres-hooks Using Postgres Hooks to protect functionality in your Postgres database. - Thu, 01 Jul 2021 02:02:30 +0000 + Thu, 01 Jul 2021 22:10:00 +0000 diff --git a/apps/www/public/planetpg-victor-rss.xml b/apps/www/public/planetpg-victor-rss.xml index e742fb6eec8..176c89edcfd 100644 --- a/apps/www/public/planetpg-victor-rss.xml +++ b/apps/www/public/planetpg-victor-rss.xml @@ -5,28 +5,28 @@ https://supabase.com/blog Latest Postgres news from Victor at Supabase en - Thu, 13 Oct 2022 02:02:30 +0000 + Thu, 13 Oct 2022 22:10:00 +0000 https://supabase.com/blog/postgres-full-text-search-vs-the-rest Postgres Full Text Search vs the rest https://supabase.com/blog/postgres-full-text-search-vs-the-rest Comparing one of the most popular Postgres features against alternatives - Thu, 13 Oct 2022 02:02:30 +0000 + Thu, 13 Oct 2022 22:10:00 +0000 https://supabase.com/blog/choosing-a-postgres-primary-key Choosing a Postgres Primary Key https://supabase.com/blog/choosing-a-postgres-primary-key Turns out the question of which identifier to use as a Primary Key is complicated -- we're going to dive into some of the complexity and inherent trade-offs, and figure things out - Wed, 07 Sep 2022 02:02:30 +0000 + Wed, 07 Sep 2022 22:10:00 +0000 https://supabase.com/blog/seen-by-in-postgresql Implementing "seen by" functionality with Postgres https://supabase.com/blog/seen-by-in-postgresql Different approaches for tracking visitor counts with PostgreSQL. - Sun, 17 Jul 2022 02:02:30 +0000 + Sun, 17 Jul 2022 22:10:00 +0000 diff --git a/apps/www/public/rss.xml b/apps/www/public/rss.xml index 2b5f39d0071..fd0d82bb06a 100644 --- a/apps/www/public/rss.xml +++ b/apps/www/public/rss.xml @@ -5,1141 +5,1148 @@ https://supabase.com Latest news from Supabase en - Fri, 31 Mar 2023 02:02:30 +0000 + Thu, 06 Apr 2023 22:10:00 +0000 + https://supabase.com/blog/infinite-scroll-with-supabase + Infinite scroll with NextJs, Supabase and Framer Motion + https://supabase.com/blog/infinite-scroll-with-supabase + Lazy load and paginate data on scroll with supabase-js and a sprinkle of Framer Motion magic ✨ + Thu, 06 Apr 2023 22:10:00 +0000 + + https://supabase.com/blog/supaclub SupaClub https://supabase.com/blog/supaclub The Worlds First Software Engineering Nightclub. - Fri, 31 Mar 2023 02:02:30 +0000 + Fri, 31 Mar 2023 22:10:00 +0000 https://supabase.com/blog/supabase-beta-update-february-2023 Supabase Beta February 2023 https://supabase.com/blog/supabase-beta-update-february-2023 There is something for everybody this month - AI, Auth, Database, Edge Functions, GraphQL … you name it! - Wed, 08 Mar 2023 03:03:30 +0000 + Wed, 08 Mar 2023 23:11:00 +0000 https://supabase.com/blog/geo-queries-with-postgis-in-ionic-angular Geo Queries with PostGIS in Ionic Angular https://supabase.com/blog/geo-queries-with-postgis-in-ionic-angular Using the PostGIS extension to build a cross-platform application with Ionic Angular. - Tue, 28 Feb 2023 03:03:30 +0000 + Tue, 28 Feb 2023 23:11:00 +0000 https://supabase.com/blog/type-constraints-in-65-lines-of-sql Type Constraints in 65 lines of SQL https://supabase.com/blog/type-constraints-in-65-lines-of-sql Creating validated data types in Postgres - Thu, 16 Feb 2023 03:03:30 +0000 + Thu, 16 Feb 2023 23:11:00 +0000 https://supabase.com/blog/case-study-happyteams HappyTeams unlocks better performance and reduces cost with Supabase https://supabase.com/blog/case-study-happyteams How a bootstrapped startup migrated from Heroku to Supabase in 30 minutes and never looked back - Wed, 15 Feb 2023 03:03:30 +0000 + Wed, 15 Feb 2023 23:11:00 +0000 https://supabase.com/blog/flutter-real-time-multiplayer-game How to build a real-time multiplayer game with Flutter Flame https://supabase.com/blog/flutter-real-time-multiplayer-game Build a real-time multiplayer game using Flutter, Flame, and Supabase realtime. - Mon, 13 Feb 2023 03:03:30 +0000 + Mon, 13 Feb 2023 23:11:00 +0000 https://supabase.com/blog/supabase-beta-january-2023 Supabase Beta January 2023 https://supabase.com/blog/supabase-beta-january-2023 New Postgres extensions, pg_graphql updates, changes to Edge Functions, and more! - Tue, 07 Feb 2023 03:03:30 +0000 + Tue, 07 Feb 2023 23:11:00 +0000 https://supabase.com/blog/chatgpt-supabase-docs Supabase Clippy: ChatGPT for Supabase Docs https://supabase.com/blog/chatgpt-supabase-docs Creating a ChatGPT interface for the Supabase documentation. - Mon, 06 Feb 2023 03:03:30 +0000 + Mon, 06 Feb 2023 23:11:00 +0000 https://supabase.com/blog/openai-embeddings-postgres-vector Storing OpenAI embeddings in Postgres with pgvector https://supabase.com/blog/openai-embeddings-postgres-vector An example of how to build an AI-powered search engine using OpenAI's embeddings and PostgreSQL. - Sun, 05 Feb 2023 03:03:30 +0000 + Sun, 05 Feb 2023 23:11:00 +0000 https://supabase.com/blog/supabase-beta-december-2022 Supabase Beta December 2022 https://supabase.com/blog/supabase-beta-december-2022 This month the Beta Update is a Launch Week 6 Special, where we review the cascade of announcements. - Wed, 04 Jan 2023 03:03:30 +0000 + Wed, 04 Jan 2023 23:11:00 +0000 https://supabase.com/blog/launch-week-6-hackathon-winners Launch Week 6 Hackathon Winners https://supabase.com/blog/launch-week-6-hackathon-winners Announcing the winners of the Launch Week 6 Hackathon! - Mon, 02 Jan 2023 03:03:30 +0000 + Mon, 02 Jan 2023 23:11:00 +0000 https://supabase.com/blog/vault-now-in-beta Supabase Vault is now in Beta https://supabase.com/blog/vault-now-in-beta A Postgres extension to store encrypted secrets and encrypt data. - Thu, 15 Dec 2022 03:03:30 +0000 + Thu, 15 Dec 2022 23:11:00 +0000 https://supabase.com/blog/postgrest-11-prerelease PostgREST 11 pre-release https://supabase.com/blog/postgrest-11-prerelease Describes new features of PostgREST 11 pre-release - Thu, 15 Dec 2022 03:03:30 +0000 + Thu, 15 Dec 2022 23:11:00 +0000 https://supabase.com/blog/postgres-point-in-time-recovery Point in Time Recovery is now available for Pro projects https://supabase.com/blog/postgres-point-in-time-recovery We're making PITR available for more projects, with a new Dashboard UI that makes it simple to use. - Thu, 15 Dec 2022 03:03:30 +0000 + Thu, 15 Dec 2022 23:11:00 +0000 https://supabase.com/blog/pg-graphql-v1 pg_graphql v1.0 https://supabase.com/blog/pg-graphql-v1 Announcing the v1.0 release of pg_graphql - Thu, 15 Dec 2022 03:03:30 +0000 + Thu, 15 Dec 2022 23:11:00 +0000 https://supabase.com/blog/new-in-postgres-15 What's new in Postgres 15? https://supabase.com/blog/new-in-postgres-15 Describes the release of Postgres 15, new features and reasons to use it - Thu, 15 Dec 2022 03:03:30 +0000 + Thu, 15 Dec 2022 23:11:00 +0000 https://supabase.com/blog/launch-week-6-wrap-up Launch Week 6: Wrap Up https://supabase.com/blog/launch-week-6-wrap-up That's a wrap on Supabase Launch Week Day 6. Here's everything we shipped in one long blog post. - Thu, 15 Dec 2022 03:03:30 +0000 + Thu, 15 Dec 2022 23:11:00 +0000 https://supabase.com/blog/launch-week-6-community-day Community Day https://supabase.com/blog/launch-week-6-community-day Wrapping up Launch Week 6 with contributors, partners, and friends. - Thu, 15 Dec 2022 03:03:30 +0000 + Thu, 15 Dec 2022 23:11:00 +0000 https://supabase.com/blog/custom-domain-names Custom Domain Names https://supabase.com/blog/custom-domain-names Change your Supabase project's domain name to your own domain. - Thu, 15 Dec 2022 03:03:30 +0000 + Thu, 15 Dec 2022 23:11:00 +0000 https://supabase.com/blog/postgres-foreign-data-wrappers-rust Supabase Wrappers, a Postgres FDW framework written in Rust https://supabase.com/blog/postgres-foreign-data-wrappers-rust A framework for building Postgres Foreign Data Wrappers which connects to Stripe, Firebase, Clickhouse, and more. - Wed, 14 Dec 2022 03:03:30 +0000 + Wed, 14 Dec 2022 23:11:00 +0000 https://supabase.com/blog/mfa-auth-via-rls Multi-factor Authentication via Row Level Security Enforcement https://supabase.com/blog/mfa-auth-via-rls MFA Auth with enforcement via RLS - Tue, 13 Dec 2022 03:03:30 +0000 + Tue, 13 Dec 2022 23:11:00 +0000 https://supabase.com/blog/storage-image-resizing-smart-cdn Supabase Storage v2: Image resizing and Smart CDN https://supabase.com/blog/storage-image-resizing-smart-cdn We're introducing new features for Supabase Storage: Image resizing and a Smart CDN. - Mon, 12 Dec 2022 03:03:30 +0000 + Mon, 12 Dec 2022 23:11:00 +0000 https://supabase.com/blog/new-supabase-docs-built-with-nextjs New Supabase Docs, built with Next.js https://supabase.com/blog/new-supabase-docs-built-with-nextjs We've redesigned our Docs and migrated to Next.js - Sun, 11 Dec 2022 03:03:30 +0000 + Sun, 11 Dec 2022 23:11:00 +0000 https://supabase.com/blog/postgres-crdt pg_crdt - an experimental CRDT extension for Postgres https://supabase.com/blog/postgres-crdt Embedding Yjs and Automerge into Postgres for collaborative applications. - Fri, 09 Dec 2022 03:03:30 +0000 + Fri, 09 Dec 2022 23:11:00 +0000 https://supabase.com/blog/who-we-hire Who We Hire at Supabase https://supabase.com/blog/who-we-hire Traits we look for to maintain a culture of shipping fast and often - Thu, 08 Dec 2022 03:03:30 +0000 + Thu, 08 Dec 2022 23:11:00 +0000 https://supabase.com/blog/launch-week-6-hackathon Launch Week 6 Hackathon https://supabase.com/blog/launch-week-6-hackathon Build an Open Source Project, Win $1500 and the Supabase Darkmode Keyboard - Thu, 08 Dec 2022 03:03:30 +0000 + Thu, 08 Dec 2022 23:11:00 +0000 https://supabase.com/blog/supabase-beta-november-2022 Supabase Beta November 2022 https://supabase.com/blog/supabase-beta-november-2022 We are preparing everything for Launch Week 6, but we still had time to ship some goodies this month! - Tue, 06 Dec 2022 03:03:30 +0000 + Tue, 06 Dec 2022 23:11:00 +0000 https://supabase.com/blog/the-supabase-content-storm The Supabase Content Storm https://supabase.com/blog/the-supabase-content-storm We worked with +30 content creators to drop a mountain of content simultaneously. - Mon, 05 Dec 2022 03:03:30 +0000 + Mon, 05 Dec 2022 23:11:00 +0000 https://supabase.com/blog/transparent-column-encryption-with-postgres Transparent Column Encryption with Postgres https://supabase.com/blog/transparent-column-encryption-with-postgres Using pgsodium's Transparent Column Encryption to encrypt data and provide your users with row-level encryption. - Wed, 30 Nov 2022 03:03:30 +0000 + Wed, 30 Nov 2022 23:11:00 +0000 https://supabase.com/blog/sql-or-nosql-both-with-postgresql SQL or NoSQL? Why not use both (with PostgreSQL)? https://supabase.com/blog/sql-or-nosql-both-with-postgresql How to turn Postgres into an easy-to-use NoSQL database that retains all the power of SQL - Wed, 23 Nov 2022 03:03:30 +0000 + Wed, 23 Nov 2022 23:11:00 +0000 https://supabase.com/blog/flutter-authentication-and-authorization-with-rls Flutter Authentication and Authorization with RLS https://supabase.com/blog/flutter-authentication-and-authorization-with-rls Learn how you can secure your Flutter app using Supabase Row Level Security. - Mon, 21 Nov 2022 03:03:30 +0000 + Mon, 21 Nov 2022 23:11:00 +0000 https://supabase.com/blog/fetching-and-caching-supabase-data-in-next-js-server-components Fetching and caching Supabase data in Next.js 13 Server Components https://supabase.com/blog/fetching-and-caching-supabase-data-in-next-js-server-components Next.js 13 introduces new data fetching and caching methods to enable React Server Components and Suspense. - Wed, 16 Nov 2022 03:03:30 +0000 + Wed, 16 Nov 2022 23:11:00 +0000 https://supabase.com/blog/authentication-in-ionic-angular Authentication in Ionic Angular with Supabase https://supabase.com/blog/authentication-in-ionic-angular Learn how to build an Ionic Angular app with authentication, Row Level Security, and Magic Link auth. - Mon, 07 Nov 2022 03:03:30 +0000 + Mon, 07 Nov 2022 23:11:00 +0000 https://supabase.com/blog/supabase-beta-update-october-2022 Supabase Beta October 2022 https://supabase.com/blog/supabase-beta-update-october-2022 New SDKs, quickstarts, Functions tricks, and more. But, more importantly, Launch Week 6️ has a date! - Tue, 01 Nov 2022 02:02:30 +0000 + Tue, 01 Nov 2022 23:11:00 +0000 https://supabase.com/blog/postgresql-commitfest What is PostgreSQL commitfest and how to contribute https://supabase.com/blog/postgresql-commitfest A time-tested method for contributing to the core Postgres code - Wed, 26 Oct 2022 02:02:30 +0000 + Wed, 26 Oct 2022 22:10:00 +0000 https://supabase.com/blog/supabase-flutter-sdk-v1-released supabase-flutter v1 Released https://supabase.com/blog/supabase-flutter-sdk-v1-released We've released supabase-flutter v1. More intuitive way of accessing Supabase from your Flutter application. - Thu, 20 Oct 2022 02:02:30 +0000 + Thu, 20 Oct 2022 22:10:00 +0000 https://supabase.com/blog/supabase-js-v2-released supabase-js v2 Released https://supabase.com/blog/supabase-js-v2-released We've released supabase-js v2. Updated examples, quickstarts, and an improved experience. - Wed, 19 Oct 2022 02:02:30 +0000 + Wed, 19 Oct 2022 22:10:00 +0000 https://supabase.com/blog/postgres-full-text-search-vs-the-rest Postgres Full Text Search vs the rest https://supabase.com/blog/postgres-full-text-search-vs-the-rest Comparing one of the most popular Postgres features against alternatives - Thu, 13 Oct 2022 02:02:30 +0000 + Thu, 13 Oct 2022 22:10:00 +0000 https://supabase.com/blog/supabase-beta-update-september-2022 Supabase Beta September 2022 https://supabase.com/blog/supabase-beta-update-september-2022 We were too focused on clearing out the backlog so we didn't ship anything new last month... or did we?! - Tue, 04 Oct 2022 02:02:30 +0000 + Tue, 04 Oct 2022 22:10:00 +0000 https://supabase.com/blog/postgres-wasm Postgres WASM by Snaplet and Supabase https://supabase.com/blog/postgres-wasm We're open sourcing postgres-wasm, a PostgresQL server that runs inside a browser, with our friends at Snaplet. - Sun, 02 Oct 2022 02:02:30 +0000 + Sun, 02 Oct 2022 22:10:00 +0000 https://supabase.com/blog/choosing-a-postgres-primary-key Choosing a Postgres Primary Key https://supabase.com/blog/choosing-a-postgres-primary-key Turns out the question of which identifier to use as a Primary Key is complicated -- we're going to dive into some of the complexity and inherent trade-offs, and figure things out - Wed, 07 Sep 2022 02:02:30 +0000 + Wed, 07 Sep 2022 22:10:00 +0000 https://supabase.com/blog/supabase-beta-update-august-2022 Supabase Beta August 2022 https://supabase.com/blog/supabase-beta-update-august-2022 Launch Week Special. See everything we shipped, plus winners of the Hackathon and the extended Community Highlights - Tue, 06 Sep 2022 02:02:30 +0000 + Tue, 06 Sep 2022 22:10:00 +0000 https://supabase.com/blog/launch-week-5-hackathon-winners Launch Week 5 Hackathon Winners https://supabase.com/blog/launch-week-5-hackathon-winners Announcing the winners of the Launch Week 5 Hackathon! - Wed, 24 Aug 2022 02:02:30 +0000 + Wed, 24 Aug 2022 22:10:00 +0000 https://supabase.com/blog/building-a-realtime-trello-board-with-supabase-and-angular Building a Realtime Trello Board with Supabase and Angular https://supabase.com/blog/building-a-realtime-trello-board-with-supabase-and-angular Go beyond the hello world example with this real world project. - Tue, 23 Aug 2022 02:02:30 +0000 + Tue, 23 Aug 2022 22:10:00 +0000 https://supabase.com/blog/supabase-vault Supabase Vault https://supabase.com/blog/supabase-vault Today we're announcing Vault, a Postgres extension for managing secrets and encryption inside your database. - Thu, 18 Aug 2022 02:02:30 +0000 + Thu, 18 Aug 2022 22:10:00 +0000 https://supabase.com/blog/postgrest-v10 PostgREST v10: EXPLAIN and Improved Relationship Detection https://supabase.com/blog/postgrest-v10 Today, PostgREST 10 was released. Let's take a look at some of the new features that go hand in hand with supabase-js v2. - Thu, 18 Aug 2022 02:02:30 +0000 + Thu, 18 Aug 2022 22:10:00 +0000 https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation pg_jsonschema: JSON Schema support for Postgres https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation Today we're releasing pg_jsonschema, a Postgres extension for JSON validation. - Thu, 18 Aug 2022 02:02:30 +0000 + Thu, 18 Aug 2022 22:10:00 +0000 https://supabase.com/blog/launch-week-5-one-more-thing One more thing https://supabase.com/blog/launch-week-5-one-more-thing Let's be honest, it's never just one more thing. - Thu, 18 Aug 2022 02:02:30 +0000 + Thu, 18 Aug 2022 22:10:00 +0000 https://supabase.com/blog/launch-week-5-community-day Community Day https://supabase.com/blog/launch-week-5-community-day Wrapping up Launch Week 5 with contributors, partners, and friends. - Thu, 18 Aug 2022 02:02:30 +0000 + Thu, 18 Aug 2022 22:10:00 +0000 https://supabase.com/blog/supabase-realtime-multiplayer-general-availability Realtime: Multiplayer Edition https://supabase.com/blog/supabase-realtime-multiplayer-general-availability Announcing the general availability of Realtime's Broadcast and Presence. - Wed, 17 Aug 2022 02:02:30 +0000 + Wed, 17 Aug 2022 22:10:00 +0000 https://supabase.com/blog/supabase-soc2 Supabase is SOC2 compliant https://supabase.com/blog/supabase-soc2 Supabase is now SOC2 compliant. Learn how we got here and what it means for our customers. - Tue, 16 Aug 2022 02:02:30 +0000 + Tue, 16 Aug 2022 22:10:00 +0000 https://supabase.com/blog/supabase-js-v2 supabase-js v2 https://supabase.com/blog/supabase-js-v2 A look at supabase-js v2, which brings type support and focuses on quality-of-life improvements for developers. - Mon, 15 Aug 2022 02:02:30 +0000 + Mon, 15 Aug 2022 22:10:00 +0000 https://supabase.com/blog/supabase-cli-v1-and-admin-api-beta Supabase CLI v1 and Management API Beta https://supabase.com/blog/supabase-cli-v1-and-admin-api-beta We are moving Supabase CLI v1 out of beta, and releasing Management API beta. - Sun, 14 Aug 2022 02:02:30 +0000 + Sun, 14 Aug 2022 22:10:00 +0000 https://supabase.com/blog/supabase-series-b Supabase Series B https://supabase.com/blog/supabase-series-b Supabase raised $80M in May, bringing our total funding to $116M. - Thu, 11 Aug 2022 02:02:30 +0000 + Thu, 11 Aug 2022 22:10:00 +0000 https://supabase.com/blog/launch-week-5-hackathon Launch Week 5 Hackathon https://supabase.com/blog/launch-week-5-hackathon Build to win $1500 - Friday 12th to Monday 21st August 2022 - Tue, 09 Aug 2022 02:02:30 +0000 + Tue, 09 Aug 2022 22:10:00 +0000 https://supabase.com/blog/slack-consolidate-slackbot-to-consolidate-messages Slack Consolidate: a slackbot built with Python and Supabase https://supabase.com/blog/slack-consolidate-slackbot-to-consolidate-messages A slackbot to consolidate messages from different channels using Supabase, Slack SDK and Python - Mon, 08 Aug 2022 02:02:30 +0000 + Mon, 08 Aug 2022 22:10:00 +0000 https://supabase.com/blog/supabase-beta-update-july-2022 Supabase Beta July 2022 https://supabase.com/blog/supabase-beta-update-july-2022 Launch Week Golden Tickets, Flutter SDK 1.0 Developer Preview and more... - Tue, 02 Aug 2022 02:02:30 +0000 + Tue, 02 Aug 2022 22:10:00 +0000 https://supabase.com/blog/supabase-flutter-sdk-1-developer-preview Supabase Flutter SDK 1.0 Developer Preview https://supabase.com/blog/supabase-flutter-sdk-1-developer-preview Supabase Flutter SDK is getting a major update and we need your help making it better. - Mon, 01 Aug 2022 02:02:30 +0000 + Mon, 01 Aug 2022 22:10:00 +0000 https://supabase.com/blog/seen-by-in-postgresql Implementing "seen by" functionality with Postgres https://supabase.com/blog/seen-by-in-postgresql Different approaches for tracking visitor counts with PostgreSQL. - Sun, 17 Jul 2022 02:02:30 +0000 + Sun, 17 Jul 2022 22:10:00 +0000 https://supabase.com/blog/supabase-auth-helpers-with-sveltekit-support Revamped Auth Helpers for Supabase (with SvelteKit support) https://supabase.com/blog/supabase-auth-helpers-with-sveltekit-support Supabase Auth Helpers now have improved developer experience, Sveltekit support, and more. - Tue, 12 Jul 2022 02:02:30 +0000 + Tue, 12 Jul 2022 22:10:00 +0000 https://supabase.com/blog/beta-update-june-2022 Supabase Beta June 2022 https://supabase.com/blog/beta-update-june-2022 Auth Helpers, Unlimited Free Projects, CLI and more... - Tue, 05 Jul 2022 02:02:30 +0000 + Tue, 05 Jul 2022 22:10:00 +0000 https://supabase.com/blog/flutter-tutorial-building-a-chat-app Flutter Tutorial: building a Flutter chat app https://supabase.com/blog/flutter-tutorial-building-a-chat-app Learn how to build a Flutter chat app with open source and scalable backend (inc. auth, realtime, database, and more). - Wed, 29 Jun 2022 02:02:30 +0000 + Wed, 29 Jun 2022 22:10:00 +0000 https://supabase.com/blog/visualizing-supabase-data-using-metabase Visualizing Supabase Data using Metabase https://supabase.com/blog/visualizing-supabase-data-using-metabase How to create different kinds of charts out of data stored in Supabase using Metabase. - Tue, 28 Jun 2022 02:02:30 +0000 + Tue, 28 Jun 2022 22:10:00 +0000 https://supabase.com/blog/partial-postgresql-data-dumps-with-rls Partial data dumps using Postgres Row Level Security https://supabase.com/blog/partial-postgresql-data-dumps-with-rls Using RLS to create seed files for local PostgreSQL testing. - Mon, 27 Jun 2022 02:02:30 +0000 + Mon, 27 Jun 2022 22:10:00 +0000 https://supabase.com/blog/loading-data-supabase-python Python data loading with Supabase https://supabase.com/blog/loading-data-supabase-python An example of how to load data into Supabase using supabase-py - Thu, 16 Jun 2022 02:02:30 +0000 + Thu, 16 Jun 2022 22:10:00 +0000 https://supabase.com/blog/beta-update-may-2022 Supabase Beta May 2022 https://supabase.com/blog/beta-update-may-2022 Product and community updates including wildcard auth redirects, edge functions with webhooks, and Prometheus endpoints for everybody. - Tue, 31 May 2022 02:02:30 +0000 + Tue, 31 May 2022 22:10:00 +0000 https://supabase.com/blog/how-supabase-accelerates-development-of-all-pull-together How Mike Lyndon is using Supabase to accelerate development of AllPullTogether https://supabase.com/blog/how-supabase-accelerates-development-of-all-pull-together Mike Lyndon is learning web development as he builds AllPullTogether, and Supabase is helping him accelerate what he can accomplish. - Wed, 25 May 2022 02:02:30 +0000 + Wed, 25 May 2022 22:10:00 +0000 https://supabase.com/blog/partner-gallery-works-with-supabase Works With Supabase - announcing our Partner Gallery https://supabase.com/blog/partner-gallery-works-with-supabase Introducing our Partner Gallery - open source and made with Supabase. - Tue, 19 Apr 2022 02:02:30 +0000 + Tue, 19 Apr 2022 22:10:00 +0000 https://supabase.com/blog/bring-the-func-hackathon-winners Bring the Func Hackathon Winners 2022 https://supabase.com/blog/bring-the-func-hackathon-winners Celebrating many amazing OSS Hackathon projects using GraphQL and Edge Functions. - Sun, 17 Apr 2022 02:02:30 +0000 + Sun, 17 Apr 2022 22:10:00 +0000 https://supabase.com/blog/beta-update-march-2022 Supabase Beta March 2022 https://supabase.com/blog/beta-update-march-2022 Functions, GraphQL, and much more. - Thu, 14 Apr 2022 02:02:30 +0000 + Thu, 14 Apr 2022 22:10:00 +0000 https://supabase.com/blog/supabrew Supabrew - Never Code Thirsty https://supabase.com/blog/supabrew A light and refreshing non-alcoholic beer for devs. - Fri, 01 Apr 2022 02:02:30 +0000 + Thu, 31 Mar 2022 22:10:00 +0000 https://supabase.com/blog/supabase-realtime-with-multiplayer-features Supabase Realtime, with Multiplayer Features https://supabase.com/blog/supabase-realtime-with-multiplayer-features Today we're announced Realtime, with multiplayer features. Realtime enables broadcast, presence, and listening to database changes delivered over WebSockets. - Fri, 01 Apr 2022 02:02:30 +0000 + Thu, 31 Mar 2022 22:10:00 +0000 https://supabase.com/blog/hackathon-bring-the-func Hackathon: Bring the Func(🕺) https://supabase.com/blog/hackathon-bring-the-func Build open-source projects with our latest features, win limited edition swag and plant a tree! - Fri, 01 Apr 2022 02:02:30 +0000 + Thu, 31 Mar 2022 22:10:00 +0000 https://supabase.com/blog/supabase-edge-functions Edge Functions are now available in Supabase https://supabase.com/blog/supabase-edge-functions Today we're launching Edge Functions. Edge Functions let you execute Typescript code close to your users, no matter where they're located. - Wed, 30 Mar 2022 02:02:30 +0000 + Wed, 30 Mar 2022 22:10:00 +0000 https://supabase.com/blog/supabase-enterprise Introducing Supabase Enterprise https://supabase.com/blog/supabase-enterprise Today we are releasing Supabase Enterprise, a suite of features to scale your project. - Tue, 29 Mar 2022 02:02:30 +0000 + Tue, 29 Mar 2022 22:10:00 +0000 https://supabase.com/blog/graphql-now-available GraphQL is now available in Supabase https://supabase.com/blog/graphql-now-available GraphQL support is now in general availability on the Supabase platform via our open source PostgreSQL extension, pg_graphql (beta). - Mon, 28 Mar 2022 02:02:30 +0000 + Mon, 28 Mar 2022 22:10:00 +0000 https://supabase.com/blog/community-day-lw4 Community Day https://supabase.com/blog/community-day-lw4 Kicking off Launch Week 4 with contributors, partners, and friends. - Sun, 27 Mar 2022 02:02:30 +0000 + Sun, 27 Mar 2022 22:10:00 +0000 https://supabase.com/blog/supabase-launch-week-four Supabase Launch Week 4 https://supabase.com/blog/supabase-launch-week-four Launch Week 4: One new feature every day for an entire week. Starting Monday 28th March. - Thu, 24 Mar 2022 02:02:30 +0000 + Thu, 24 Mar 2022 23:11:00 +0000 https://supabase.com/blog/should-i-open-source-my-company Should I Open Source my Company? https://supabase.com/blog/should-i-open-source-my-company The unexpected upsides of building in public - Thu, 24 Mar 2022 02:02:30 +0000 + Thu, 24 Mar 2022 23:11:00 +0000 https://supabase.com/blog/audit Postgres Auditing in 150 lines of SQL https://supabase.com/blog/audit PostgreSQL has a robust set of features which we can leverage to create a generic auditing solution in 150 lines of SQL. - Mon, 07 Mar 2022 03:03:30 +0000 + Mon, 07 Mar 2022 23:11:00 +0000 https://supabase.com/blog/supabase-beta-january-2022 Supabase Beta January 2022 https://supabase.com/blog/supabase-beta-january-2022 New auth providers, SMS providers, and new videos. - Mon, 21 Feb 2022 03:03:30 +0000 + Mon, 21 Feb 2022 23:11:00 +0000 https://supabase.com/blog/supabase-beta-december-2021 Supabase Beta December 2021 https://supabase.com/blog/supabase-beta-december-2021 New crypto extension, Postgres videos, and a bunch of cool integrations. - Wed, 19 Jan 2022 03:03:30 +0000 + Wed, 19 Jan 2022 23:11:00 +0000 https://supabase.com/blog/product-hunt-golden-kitty-awards-2021 Golden Kitty Awards Ceremony Watch Party with Supabase https://supabase.com/blog/product-hunt-golden-kitty-awards-2021 Hang out with us while watching the Product Hunt Golden Kitty Awards Ceremony - Wed, 19 Jan 2022 03:03:30 +0000 + Wed, 19 Jan 2022 23:11:00 +0000 https://supabase.com/blog/holiday-hackdays-winners-2021 Holiday Hackdays Winners 2021 https://supabase.com/blog/holiday-hackdays-winners-2021 Celebrating many amazing projects submitted to our Holiday Hackdays Hackathon. - Thu, 16 Dec 2021 03:03:30 +0000 + Thu, 16 Dec 2021 23:11:00 +0000 https://supabase.com/blog/beta-november-2021-launch-week-recap Supabase Beta November 2021: Launch Week Recap https://supabase.com/blog/beta-november-2021-launch-week-recap We wrapped up November with Supabase's third Launch Week. Here's all the awesome stuff that got shipped ... - Tue, 14 Dec 2021 03:03:30 +0000 + Tue, 14 Dec 2021 23:11:00 +0000 https://supabase.com/blog/supabase-holiday-hackdays-hackathon Kicking off the Holiday Hackdays https://supabase.com/blog/supabase-holiday-hackdays-hackathon Build cool stuff and celebrate open-source software with us during the Holiday Hackdays! - Thu, 02 Dec 2021 03:03:30 +0000 + Thu, 02 Dec 2021 23:11:00 +0000 https://supabase.com/blog/pg-graphql pg_graphql: A GraphQL extension for PostgreSQL https://supabase.com/blog/pg-graphql GraphQL support is in development for PostgreSQL + Supabase. - Thu, 02 Dec 2021 03:03:30 +0000 + Thu, 02 Dec 2021 23:11:00 +0000 https://supabase.com/blog/launch-week-three-friday-five-more-things Five more things https://supabase.com/blog/launch-week-three-friday-five-more-things It's never just one more thing! - Thu, 02 Dec 2021 03:03:30 +0000 + Thu, 02 Dec 2021 23:11:00 +0000 https://supabase.com/blog/supabase-acquires-logflare Supabase acquires Logflare https://supabase.com/blog/supabase-acquires-logflare Today, we're ecstatic to announce that Logflare is joining Supabase. - Wed, 01 Dec 2021 03:03:30 +0000 + Wed, 01 Dec 2021 23:11:00 +0000 https://supabase.com/blog/realtime-row-level-security-in-postgresql Realtime Postgres RLS now available on Supabase https://supabase.com/blog/realtime-row-level-security-in-postgresql Realtime database changes are now broadcast to authenticated users, respecting the same PostgreSQL policies that you use for Row Level Security. - Tue, 30 Nov 2021 03:03:30 +0000 + Tue, 30 Nov 2021 23:11:00 +0000 https://supabase.com/blog/supabase-studio Supabase Studio https://supabase.com/blog/supabase-studio The same Dashboard that you're using on our Platform is now available for local development and Self-Hosting. - Mon, 29 Nov 2021 03:03:30 +0000 + Mon, 29 Nov 2021 23:11:00 +0000 https://supabase.com/blog/community-day-lw3 Community Day https://supabase.com/blog/community-day-lw3 Kicking off launch week by highlighting the communities around Supabase. - Sun, 28 Nov 2021 03:03:30 +0000 + Sun, 28 Nov 2021 23:11:00 +0000 https://supabase.com/blog/whats-new-in-postgres-14 New in PostgreSQL 14: What every developer should know https://supabase.com/blog/whats-new-in-postgres-14 A quick look at some new features and functionality in PostgreSQL 14. - Sat, 27 Nov 2021 03:03:30 +0000 + Sat, 27 Nov 2021 23:11:00 +0000 https://supabase.com/blog/postgrest-9 PostgREST 9 https://supabase.com/blog/postgrest-9 New features and updates in PostgREST version 9. - Fri, 26 Nov 2021 03:03:30 +0000 + Fri, 26 Nov 2021 23:11:00 +0000 https://supabase.com/blog/supabase-launch-week-the-trilogy Supabase Launch Week III: Holiday Special https://supabase.com/blog/supabase-launch-week-the-trilogy Tis the season to be shipping. - Thu, 25 Nov 2021 03:03:30 +0000 + Thu, 25 Nov 2021 23:11:00 +0000 https://supabase.com/blog/supabase-how-we-launch How we launch at Supabase https://supabase.com/blog/supabase-how-we-launch The history and methodology of Supabase Launch Week. - Thu, 25 Nov 2021 03:03:30 +0000 + Thu, 25 Nov 2021 23:11:00 +0000 https://supabase.com/blog/supabase-beta-october-2021 Supabase Beta October 2021 https://supabase.com/blog/supabase-beta-october-2021 Three new Auth providers, multi-schema support, and we're gearing up for another Launch Week. - Sat, 06 Nov 2021 02:02:30 +0000 + Sat, 06 Nov 2021 23:11:00 +0000 https://supabase.com/blog/supabase-series-a Supabase $30m Series A https://supabase.com/blog/supabase-series-a Supabase just raised $30M, bringing our total funding to $36M. - Wed, 27 Oct 2021 02:02:30 +0000 + Wed, 27 Oct 2021 22:10:00 +0000 https://supabase.com/blog/replenysh-time-to-value-in-less-than-24-hours Replenysh uses Supabase to implement OTP in less than 24-hours https://supabase.com/blog/replenysh-time-to-value-in-less-than-24-hours Learn how Replenysh uses Supabase to power the circular economy, redefining how brands interact with their customers and products. - Mon, 18 Oct 2021 02:02:30 +0000 + Mon, 18 Oct 2021 22:10:00 +0000 https://supabase.com/blog/hacktoberfest-hackathon-winners-2021 Hacktoberfest Hackathon Winners 2021 https://supabase.com/blog/hacktoberfest-hackathon-winners-2021 Celebrating many amazing projects submitted to our Hacktoberfest Hackathon. - Wed, 13 Oct 2021 02:02:30 +0000 + Wed, 13 Oct 2021 22:10:00 +0000 https://supabase.com/blog/supabase-beta-sept-2021 Supabase Beta Sept 2021 https://supabase.com/blog/supabase-beta-sept-2021 Hackathon, Aborting request, UI updates, and now Hiring. - Sun, 03 Oct 2021 02:02:30 +0000 + Sun, 03 Oct 2021 22:10:00 +0000 https://supabase.com/blog/supabase-hacktoberfest-hackathon-2021 Supabase Hacktoberfest Hackathon 2021 https://supabase.com/blog/supabase-hacktoberfest-hackathon-2021 We're running another Supabase Hackathon during Hacktoberfest! - Mon, 27 Sep 2021 02:02:30 +0000 + Mon, 27 Sep 2021 22:10:00 +0000 https://supabase.com/blog/supabase-beta-august-2021 Supabase Beta August 2021 https://supabase.com/blog/supabase-beta-august-2021 Fundraising, Realtime Security, custom SMS templates, and deployments in South Korea. - Thu, 09 Sep 2021 02:02:30 +0000 + Thu, 09 Sep 2021 22:10:00 +0000 https://supabase.com/blog/supabase-beta-july-2021 Supabase Beta July 2021 https://supabase.com/blog/supabase-beta-july-2021 Discord Logins, Vercel Integration, Full text search, and OAuth guides. - Wed, 11 Aug 2021 02:02:30 +0000 + Wed, 11 Aug 2021 22:10:00 +0000 https://supabase.com/blog/hackathon-winners Open Source Hackathon Winners https://supabase.com/blog/hackathon-winners Let the medal ceremony begin for the best projects submitted during the Supabase Hackathon. - Sun, 08 Aug 2021 02:02:30 +0000 + Sun, 08 Aug 2021 22:10:00 +0000 https://supabase.com/blog/supabase-swag-store Supabase Swag Store https://supabase.com/blog/supabase-swag-store Today we are officially launching the Supabase Swag Store. - Thu, 29 Jul 2021 02:02:30 +0000 + Thu, 29 Jul 2021 22:10:00 +0000 https://supabase.com/blog/supabase-functions-updates Updates for Supabase Functions https://supabase.com/blog/supabase-functions-updates The question on everyone's mind - are we launching Supabase Functions? Well, it's complicated. - Thu, 29 Jul 2021 02:02:30 +0000 + Thu, 29 Jul 2021 22:10:00 +0000 https://supabase.com/blog/1-the-supabase-hackathon The Supabase Hackathon https://supabase.com/blog/1-the-supabase-hackathon A whole week of Hacking for Fun and Prizes. - Thu, 29 Jul 2021 02:02:30 +0000 + Thu, 29 Jul 2021 22:10:00 +0000 https://supabase.com/blog/supabase-reports-and-metrics Supabase Reports and Metrics https://supabase.com/blog/supabase-reports-and-metrics We're exposing a full set of metrics in your projects, so that you can build better (and faster) products for your users. - Wed, 28 Jul 2021 02:02:30 +0000 + Wed, 28 Jul 2021 22:10:00 +0000 https://supabase.com/blog/supabase-auth-passwordless-sms-login Supabase Auth v2: Phone Auth now available https://supabase.com/blog/supabase-auth-passwordless-sms-login Phone Auth is available today on all new and existing Supabase projects. - Tue, 27 Jul 2021 02:02:30 +0000 + Tue, 27 Jul 2021 22:10:00 +0000 https://supabase.com/blog/mobbin-supabase-200000-users Mobbin uses Supabase to authenticate 200,000 users https://supabase.com/blog/mobbin-supabase-200000-users Learn how Mobbin migrated 200,000 users from Firebase for a better authentication experience. - Tue, 27 Jul 2021 02:02:30 +0000 + Tue, 27 Jul 2021 22:10:00 +0000 https://supabase.com/blog/storage-beta Supabase Storage now in Beta https://supabase.com/blog/storage-beta Supabase Storage moves into Beta. - Mon, 26 Jul 2021 02:02:30 +0000 + Mon, 26 Jul 2021 22:10:00 +0000 https://supabase.com/blog/spot-flutter-with-postgres Spot: a video sharing app built with Flutter https://supabase.com/blog/spot-flutter-with-postgres Spot is a geolocation-based video-sharing app with some social networking features. - Mon, 26 Jul 2021 02:02:30 +0000 + Mon, 26 Jul 2021 22:10:00 +0000 https://supabase.com/blog/supabase-postgres-13 Supabase is now on Postgres 13.3 https://supabase.com/blog/supabase-postgres-13 From today, new Supabase projects will be on a version of Supabase Postgres that runs on Postgres 13.3. - Sun, 25 Jul 2021 02:02:30 +0000 + Sun, 25 Jul 2021 22:10:00 +0000 https://supabase.com/blog/supabase-community-day Supabase Community Day https://supabase.com/blog/supabase-community-day Community Day - Sun, 25 Jul 2021 02:02:30 +0000 + Sun, 25 Jul 2021 22:10:00 +0000 https://supabase.com/blog/epsilon3-self-hosting Epsilon3 Self-Host Supabase To Revolutionize Space Operations https://supabase.com/blog/epsilon3-self-hosting Learn how the team at Epsilon3 use Supabase to help teams execute secure and reliable operations in an industry that project spend runs into the billions. - Sun, 25 Jul 2021 02:02:30 +0000 + Sun, 25 Jul 2021 22:10:00 +0000 https://supabase.com/blog/supabase-launch-week-sql Supabase Launch Week II: The SQL https://supabase.com/blog/supabase-launch-week-sql Five days of Supabase. Again. - Wed, 21 Jul 2021 02:02:30 +0000 + Wed, 21 Jul 2021 22:10:00 +0000 https://supabase.com/blog/roles-postgres-hooks Protecting reserved roles with PostgreSQL Hooks https://supabase.com/blog/roles-postgres-hooks Using Postgres Hooks to protect functionality in your Postgres database. - Thu, 01 Jul 2021 02:02:30 +0000 + Thu, 01 Jul 2021 22:10:00 +0000 https://supabase.com/blog/supabase-beta-june-2021 Supabase Beta June 2021 https://supabase.com/blog/supabase-beta-june-2021 Discord Logins, Vercel Integration, Full text search, and OAuth guides. - Tue, 01 Jun 2021 02:02:30 +0000 + Tue, 01 Jun 2021 22:10:00 +0000 https://supabase.com/blog/supabase-beta-may-2021 Supabase Beta May 2021 https://supabase.com/blog/supabase-beta-may-2021 Apple & Twitter Logins, Supabase Grid, Go & Swift Libraries. - Tue, 01 Jun 2021 02:02:30 +0000 + Tue, 01 Jun 2021 22:10:00 +0000 https://supabase.com/blog/supabase-beta-april-2021 Supabase Beta April 2021 https://supabase.com/blog/supabase-beta-april-2021 Supabase "gardening" - stability, security, and community support. - Tue, 04 May 2021 02:02:30 +0000 + Tue, 04 May 2021 22:10:00 +0000 https://supabase.com/blog/supabase-beta-march-2021 Supabase Beta March 2021 https://supabase.com/blog/supabase-beta-march-2021 Launch week, Storage, Supabase CLI, Connection Pooling, Supabase UI, and Pricing. - Mon, 05 Apr 2021 02:02:30 +0000 + Mon, 05 Apr 2021 22:10:00 +0000 https://supabase.com/blog/supabase-workflows Workflows are coming to Supabase https://supabase.com/blog/supabase-workflows Functions are great, but you know what's better? - Thu, 01 Apr 2021 02:02:30 +0000 + Thu, 01 Apr 2021 22:10:00 +0000 https://supabase.com/blog/supabase-pgbouncer PgBouncer is now available in Supabase https://supabase.com/blog/supabase-pgbouncer Better support for Serverless and Postgres. - Thu, 01 Apr 2021 02:02:30 +0000 + Thu, 01 Apr 2021 22:10:00 +0000 https://supabase.com/blog/supabase-dot-com Supabase Dot Com https://supabase.com/blog/supabase-dot-com The Supabase Domain name is changing. - Thu, 01 Apr 2021 02:02:30 +0000 + Thu, 01 Apr 2021 22:10:00 +0000 https://supabase.com/blog/supabase-nft-marketplace Supabase Launches NFT Marketplace https://supabase.com/blog/supabase-nft-marketplace A fully encrypted NFT platform to protect and transact your digital assets - Thu, 01 Apr 2021 02:02:30 +0000 + Wed, 31 Mar 2021 22:10:00 +0000 https://supabase.com/blog/supabase-cli Supabase CLI https://supabase.com/blog/supabase-cli Local development, database migrations, and self-hosting. - Wed, 31 Mar 2021 02:02:30 +0000 + Tue, 30 Mar 2021 22:10:00 +0000 https://supabase.com/blog/supabase-storage Storage is now available in Supabase https://supabase.com/blog/supabase-storage Launching Supabase Storage and how you can use it in your apps - Tue, 30 Mar 2021 02:02:30 +0000 + Mon, 29 Mar 2021 22:10:00 +0000 https://supabase.com/blog/pricing Supabase Beta Pricing https://supabase.com/blog/pricing Supabase launches Beta pricing structure - Mon, 29 Mar 2021 02:02:30 +0000 + Sun, 28 Mar 2021 22:10:00 +0000 https://supabase.com/blog/launch-week Launch week https://supabase.com/blog/launch-week Five days of Supabase. - Wed, 24 Mar 2021 02:02:30 +0000 + Wed, 24 Mar 2021 23:11:00 +0000 https://supabase.com/blog/angels-of-supabase Angels of Supabase https://supabase.com/blog/angels-of-supabase Meet the investors of Supabase. - Wed, 24 Mar 2021 02:02:30 +0000 + Wed, 24 Mar 2021 23:11:00 +0000 https://supabase.com/blog/In-The-Loop Developers stay up to date with intheloop.dev https://supabase.com/blog/In-The-Loop Learn why Kevin is building intheloop.dev with Supabase - Mon, 22 Mar 2021 02:02:30 +0000 + Sun, 21 Mar 2021 23:11:00 +0000 https://supabase.com/blog/using-supabase-replit Using Supabase in Replit https://supabase.com/blog/using-supabase-replit Free hosted relational database from within your node.js repl - Thu, 11 Mar 2021 03:03:30 +0000 + Wed, 10 Mar 2021 23:11:00 +0000 https://supabase.com/blog/toad-a-link-shortener-with-simple-apis-for-low-coders Toad, a link shortener with simple APIs for low-coders https://supabase.com/blog/toad-a-link-shortener-with-simple-apis-for-low-coders An easy-to-use link shortening tool with simple APIs - Mon, 08 Mar 2021 03:03:30 +0000 + Sun, 07 Mar 2021 23:11:00 +0000 https://supabase.com/blog/postgres-as-a-cron-server Postgres as a CRON Server https://supabase.com/blog/postgres-as-a-cron-server Running repetitive tasks with your Postgres database. - Fri, 05 Mar 2021 03:03:30 +0000 + Thu, 04 Mar 2021 23:11:00 +0000 https://supabase.com/blog/supabase-beta-february-2021 Supabase Beta February 2021 https://supabase.com/blog/supabase-beta-february-2021 One year of building. - Tue, 02 Mar 2021 03:03:30 +0000 + Mon, 01 Mar 2021 23:11:00 +0000 https://supabase.com/blog/cracking-postgres-interview Cracking PostgreSQL Interview Questions https://supabase.com/blog/cracking-postgres-interview Understand the top PostgreSQL Interview Questions - Sat, 27 Feb 2021 03:03:30 +0000 + Fri, 26 Feb 2021 23:11:00 +0000 https://supabase.com/blog/case-study-roboflow Roboflow.com choose Supabase to power Paint.wtf leaderboard https://supabase.com/blog/case-study-roboflow Learn how Roboflow.com used Supabase to build their Paint.wtf leaderboard - Tue, 09 Feb 2021 03:03:30 +0000 + Mon, 08 Feb 2021 23:11:00 +0000 https://supabase.com/blog/supabase-beta-january-2021 Supabase Beta January 2021 https://supabase.com/blog/supabase-beta-january-2021 Eleven months of building. - Tue, 02 Feb 2021 03:03:30 +0000 + Mon, 01 Feb 2021 23:11:00 +0000 https://supabase.com/blog/supabase-beta-december-2020 Supabase Beta December 2020 https://supabase.com/blog/supabase-beta-december-2020 Ten months of building. - Sat, 02 Jan 2021 03:03:30 +0000 + Fri, 01 Jan 2021 23:11:00 +0000 https://supabase.com/blog/supabase-dashboard-performance Making the Supabase Dashboard Supa-fast https://supabase.com/blog/supabase-dashboard-performance Improving the performance of the Supabase dashboard - Sun, 13 Dec 2020 03:03:30 +0000 + Sat, 12 Dec 2020 23:11:00 +0000 https://supabase.com/blog/supabase-striveschool Supabase Partners With Strive School To Help Teach Open Source https://supabase.com/blog/supabase-striveschool Supabase Partners With Strive School To Help Teach Open Source To The Next Generation Of Developers - Wed, 02 Dec 2020 03:03:30 +0000 + Tue, 01 Dec 2020 23:11:00 +0000 https://supabase.com/blog/case-study-xendit Xendit Built a Counter-Fraud Watchlist for the Fintech Industry https://supabase.com/blog/case-study-xendit See how Xendit use Supabase to build a full-text search engine. - Wed, 02 Dec 2020 03:03:30 +0000 + Tue, 01 Dec 2020 23:11:00 +0000 https://supabase.com/blog/case-study-tayfa TAYFA Built a No-Code Website Builder in Seven Days https://supabase.com/blog/case-study-tayfa See how Tayfa went from idea to paying customer in less than 30 days. - Wed, 02 Dec 2020 03:03:30 +0000 + Tue, 01 Dec 2020 23:11:00 +0000 https://supabase.com/blog/case-study-monitoro Monitoro Built a Web Crawler Handling Millions of API Requests https://supabase.com/blog/case-study-monitoro See how Monitoro built an automated scraping platform using Supabase. - Wed, 02 Dec 2020 03:03:30 +0000 + Tue, 01 Dec 2020 23:11:00 +0000 https://supabase.com/blog/supabase-alpha-november-2020 Supabase Alpha November 2020 https://supabase.com/blog/supabase-alpha-november-2020 Nine months of building. - Tue, 01 Dec 2020 03:03:30 +0000 + Mon, 30 Nov 2020 23:11:00 +0000 https://supabase.com/blog/postgresql-views Postgres Views https://supabase.com/blog/postgresql-views Creating and using a view in PostgreSQL. - Wed, 18 Nov 2020 03:03:30 +0000 + Tue, 17 Nov 2020 23:11:00 +0000 https://supabase.com/blog/supabase-alpha-october-2020 Supabase Alpha October 2020 https://supabase.com/blog/supabase-alpha-october-2020 Eight months of building. - Mon, 02 Nov 2020 03:03:30 +0000 + Sun, 01 Nov 2020 23:11:00 +0000 https://supabase.com/blog/improved-dx Supabase.js 1.0 https://supabase.com/blog/improved-dx We're releasing a new version of our Supabase client with some awesome new improvements. - Fri, 30 Oct 2020 02:02:30 +0000 + Thu, 29 Oct 2020 23:11:00 +0000 https://supabase.com/blog/supabase-alpha-september-2020 Supabase Alpha September 2020 https://supabase.com/blog/supabase-alpha-september-2020 Seven months of building. - Sat, 03 Oct 2020 02:02:30 +0000 + Fri, 02 Oct 2020 22:10:00 +0000 https://supabase.com/blog/supabase-hacktoberfest-2020 Supabase Hacktoberfest 2020 https://supabase.com/blog/supabase-hacktoberfest-2020 Join us for a celebration of open source software and learn how to contribute to Supabase. - Fri, 11 Sep 2020 02:02:30 +0000 + Thu, 10 Sep 2020 22:10:00 +0000 https://supabase.com/blog/supabase-alpha-august-2020 Supabase Alpha August 2020 https://supabase.com/blog/supabase-alpha-august-2020 Six months of building - Thu, 03 Sep 2020 02:02:30 +0000 + Wed, 02 Sep 2020 22:10:00 +0000 https://supabase.com/blog/supabase-auth Supabase Auth https://supabase.com/blog/supabase-auth Authenticate and authorize your users with Supabase Auth - Wed, 05 Aug 2020 02:02:30 +0000 + Tue, 04 Aug 2020 22:10:00 +0000 https://supabase.com/blog/supabase-alpha-july-2020 Supabase Alpha July 2020 https://supabase.com/blog/supabase-alpha-july-2020 Five months of building - Sun, 02 Aug 2020 02:02:30 +0000 + Sat, 01 Aug 2020 22:10:00 +0000 https://supabase.com/blog/continuous-postgresql-backup-walg Continuous PostgreSQL Backups using WAL-G https://supabase.com/blog/continuous-postgresql-backup-walg Have you ever wanted to restore your database's state to a particular moment in time? This post explains how, using WAL-G. - Sun, 02 Aug 2020 02:02:30 +0000 + Sat, 01 Aug 2020 22:10:00 +0000 https://supabase.com/blog/alpha-launch-postmortem Alpha Launch Postmortem https://supabase.com/blog/alpha-launch-postmortem Everything that went wrong with Supabase's launch - Fri, 10 Jul 2020 02:02:30 +0000 + Thu, 09 Jul 2020 22:10:00 +0000 https://supabase.com/blog/postgresql-templates What are PostgreSQL Templates? https://supabase.com/blog/postgresql-templates What are PostgreSQL templates and what are they used for? - Thu, 09 Jul 2020 02:02:30 +0000 + Wed, 08 Jul 2020 22:10:00 +0000 https://supabase.com/blog/postgresql-physical-logical-backups Physical vs Logical Backups in PostgreSQL https://supabase.com/blog/postgresql-physical-logical-backups What are physical and logical backups in Postgres? - Tue, 07 Jul 2020 02:02:30 +0000 + Mon, 06 Jul 2020 22:10:00 +0000 https://supabase.com/blog/supabase-alpha-june-2020 Supabase Alpha June 2020 https://supabase.com/blog/supabase-alpha-june-2020 Four months of building - Wed, 01 Jul 2020 02:02:30 +0000 + Tue, 30 Jun 2020 22:10:00 +0000 https://supabase.com/blog/supabase-steve-chavez Steve Chavez has joined Supabase https://supabase.com/blog/supabase-steve-chavez Steve joins Supabase to help build Auth. - Mon, 15 Jun 2020 02:02:30 +0000 + Sun, 14 Jun 2020 22:10:00 +0000 https://supabase.com/blog/supabase-alpha-may-2020 Supabase Alpha May 2020 https://supabase.com/blog/supabase-alpha-may-2020 Three months of building - Mon, 01 Jun 2020 02:02:30 +0000 + Sun, 31 May 2020 22:10:00 +0000 https://supabase.com/blog/supabase-alpha-april-2020 Supabase Alpha April 2020 https://supabase.com/blog/supabase-alpha-april-2020 Two months of building - Mon, 01 Jun 2020 02:02:30 +0000 + Sun, 31 May 2020 22:10:00 +0000 diff --git a/docker/.env.example b/docker/.env.example index 734367c90c4..e39eb8f4f0b 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -74,14 +74,27 @@ STUDIO_DEFAULT_PROJECT=Default Project STUDIO_PORT=3000 # replace if you intend to use Studio outside of localhost -SUPABASE_PUBLIC_URL=http://localhost:8000 +SUPABASE_PUBLIC_URL=http://localhost:8000 # Enable webp support IMGPROXY_ENABLE_WEBP_DETECTION=true -# Configure Logflare API Calls -LOGFLARE_API_KEY=api-key -LOGFLARE_URL=http://localhost:54329 -# Enable logging and observability in the UI -NEXT_PUBLIC_ENABLE_LOGS=true +############ +# Logs - Configuration for Logflare +# Please refer to https://supabase.com/docs/reference/self-hosting-analytics/introduction +############ + +LOGFLARE_LOGGER_BACKEND_API_KEY=your-super-secret-and-long-logflare-key + +# Change vector.toml sinks to reflect this change +LOGFLARE_HTTP_PORT=4001 +LOGFLARE_API_KEY=your-super-secret-and-long-logflare-key + +# Change vector.toml sources.docker_syslog to reflect this change +VECTOR_PORT=9000 +VECTOR_API_PORT=9001 + +# Google Cloud Project details +GOOGLE_PROJECT_ID=GOOGLE_PROJECT_ID +GOOGLE_PROJECT_NUMBER=GOOGLE_PROJECT_NUMBER \ No newline at end of file diff --git a/docker/docker-compose-logging.yml b/docker/docker-compose-logging.yml new file mode 100644 index 00000000000..60080a2e6c2 --- /dev/null +++ b/docker/docker-compose-logging.yml @@ -0,0 +1,153 @@ +# Usage +# Start: docker compose -f docker-compose.yml -f docker-compose-logging.yml up +# With helpers: docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml -f docker-compose-logging.yml up +# Stop: docker compose down +# Destroy: docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml -f docker-compose-logging.yml down -v --remove-orphans +# Please refer to https://supabase.com/docs/reference/self-hosting-analytics/introduction +version: "3.8" + +services: + studio: + logging: + driver: syslog + options: + syslog-address: "tcp://localhost:${VECTOR_PORT}" + tag: studio + depends_on: + analytics: + condition: service_healthy + environment: + LOGFLARE_API_KEY: ${LOGFLARE_API_KEY} + LOGFLARE_URL: http://analytics:4000 + NEXT_PUBLIC_ENABLE_LOGS: true + + kong: + logging: + driver: syslog + options: + syslog-address: "tcp://localhost:${VECTOR_PORT}" + tag: kong + depends_on: + analytics: + condition: service_healthy + + auth: + depends_on: + analytics: + condition: service_healthy + logging: + driver: syslog + options: + syslog-address: "tcp://localhost:${VECTOR_PORT}" + tag: auth + + rest: + depends_on: + analytics: + condition: service_healthy + logging: + driver: syslog + options: + syslog-address: "tcp://localhost:${VECTOR_PORT}" + tag: rest + + realtime: + depends_on: + analytics: + condition: service_healthy + logging: + driver: syslog + options: + syslog-address: "tcp://localhost:${VECTOR_PORT}" + tag: realtime + + storage: + depends_on: + analytics: + condition: service_healthy + logging: + driver: syslog + options: + syslog-address: "tcp://localhost:${VECTOR_PORT}" + tag: storage + + meta: + depends_on: + analytics: + condition: service_healthy + logging: + driver: syslog + options: + syslog-address: "tcp://localhost:${VECTOR_PORT}" + tag: meta + + analytics: + container_name: supabase-analytics + image: supabase/logflare:1.0.1 + healthcheck: + test: [ "CMD", "curl", "http://localhost:4000/health" ] + timeout: 5s + interval: 5s + retries: 10 + restart: unless-stopped + depends_on: + db: + # Disable this if you are using an external Postgres database + condition: service_healthy + ports: + - ${LOGFLARE_HTTP_PORT}:4000/tcp + volumes: + - type: bind + source: ${PWD}/gcloud.json + target: /opt/app/rel/logflare/bin/gcloud.json + read_only: true + environment: + MY_POD_IP: 127.0.0.1 + DB_USERNAME: supabase_admin + DB_DATABASE: ${POSTGRES_DB} + DB_HOSTNAME: ${POSTGRES_HOST} + DB_PORT: ${POSTGRES_PORT} + DB_PASSWORD: ${POSTGRES_PASSWORD} + DB_SCHEMA: _analytics + LOGFLARE_API_KEY: ${LOGFLARE_API_KEY} + GOOGLE_PROJECT_ID: ${GOOGLE_PROJECT_ID} + GOOGLE_PROJECT_NUMBER: ${GOOGLE_PROJECT_NUMBER} + LOGFLARE_SINGLE_TENANT: true + LOGFLARE_SUPABASE_MODE: true + LOGFLARE_MIN_CLUSTER_SIZE: 1 + vector: + container_name: supabase-vector + image: timberio/vector:0.28.1-alpine + healthcheck: + test: + [ + + "CMD", + "wget", + "--no-verbose", + "--tries=1", + "--spider", + "http://localhost:${VECTOR_API_PORT}/health" + ] + timeout: 5s + interval: 5s + retries: 3 + ports: + - ${VECTOR_PORT}:${VECTOR_PORT} + - ${VECTOR_API_PORT}:${VECTOR_API_PORT} + volumes: + - ./volumes/logs/vector.yml:/etc/vector/vector.yml:ro + command: [ "--config", "etc/vector/vector.yml" ] + # Comment out everything below this point if you are using an external Postgres database + db: + logging: + driver: syslog + options: + syslog-address: "tcp://localhost:${VECTOR_PORT}" + tag: db + depends_on: + vector: + # Disable this if you are using an external Postgres database + condition: service_healthy + volumes: + - ./volumes/db/logs.sql:/docker-entrypoint-initdb.d/migrations/99-logs.sql:Z diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 6c4247323ec..067633f138e 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -9,10 +9,16 @@ version: "3.8" services: studio: container_name: supabase-studio - image: supabase/studio:20230216-e731b77 + image: supabase/studio:20230330-99fed3d restart: unless-stopped healthcheck: - test: [ "CMD", "node", "-e", "require('http').get('http://localhost:3000/api/profile', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})" ] + test: + [ + "CMD", + "node", + "-e", + "require('http').get('http://localhost:3000/api/profile', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})" + ] timeout: 5s interval: 5s retries: 3 @@ -53,10 +59,19 @@ services: container_name: supabase-auth image: supabase/gotrue:v2.47.0 depends_on: - db: # Disable this if you are using an external Postgres database + db: + # Disable this if you are using an external Postgres database condition: service_healthy healthcheck: - test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9999/health" ] + test: + [ + "CMD", + "wget", + "--no-verbose", + "--tries=1", + "--spider", + "http://localhost:9999/health" + ] timeout: 5s interval: 5s retries: 3 @@ -102,7 +117,8 @@ services: container_name: supabase-rest image: postgrest/postgrest:v10.1.2 depends_on: - db: # Disable this if you are using an external Postgres database + db: + # Disable this if you are using an external Postgres database condition: service_healthy restart: unless-stopped environment: @@ -116,10 +132,17 @@ services: container_name: realtime-dev.supabase-realtime image: supabase/realtime:v2.5.1 depends_on: - db: # Disable this if you are using an external Postgres database + db: + # Disable this if you are using an external Postgres database condition: service_healthy healthcheck: - test: [ "CMD", "bash", "-c", "printf \\0 > /dev/tcp/localhost/4000" ] + test: + [ + "CMD", + "bash", + "-c", + "printf \\0 > /dev/tcp/localhost/4000" + ] timeout: 5s interval: 5s retries: 3 @@ -147,14 +170,23 @@ services: container_name: supabase-storage image: supabase/storage-api:v0.28.2 depends_on: - db: # Disable this if you are using an external Postgres database + db: + # Disable this if you are using an external Postgres database condition: service_healthy rest: condition: service_started imgproxy: condition: service_started healthcheck: - test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5000/status" ] + test: + [ + "CMD", + "wget", + "--no-verbose", + "--tries=1", + "--spider", + "http://localhost:5000/status" + ] timeout: 5s interval: 5s retries: 3 @@ -197,7 +229,8 @@ services: container_name: supabase-meta image: supabase/postgres-meta:v0.60.7 depends_on: - db: # Disable this if you are using an external Postgres database + db: + # Disable this if you are using an external Postgres database condition: service_healthy restart: unless-stopped environment: @@ -243,3 +276,5 @@ services: - ./volumes/db/roles.sql:/docker-entrypoint-initdb.d/init-scripts/99-roles.sql:Z # PGDATA directory is persisted between restarts - ./volumes/db/data:/var/lib/postgresql/data:Z + +# If you want to use logging, please run with `docker compose -f docker-compose.yml -f docker-compose-logging.yml up` diff --git a/docker/volumes/api/kong.yml b/docker/volumes/api/kong.yml index d10738334f1..eb1a079eb27 100644 --- a/docker/volumes/api/kong.yml +++ b/docker/volumes/api/kong.yml @@ -1,4 +1,4 @@ -_format_version: "1.1" +_format_version: '1.1' ### ### Consumers / Users @@ -55,7 +55,7 @@ services: ## Secure Auth routes - name: auth-v1 - _comment: "GoTrue: /auth/v1/* -> http://auth:9999/*" + _comment: 'GoTrue: /auth/v1/* -> http://auth:9999/*' url: http://auth:9999/ routes: - name: auth-v1-all @@ -76,7 +76,7 @@ services: ## Secure REST routes - name: rest-v1 - _comment: "PostgREST: /rest/v1/* -> http://rest:3000/*" + _comment: 'PostgREST: /rest/v1/* -> http://rest:3000/*' url: http://rest:3000/ routes: - name: rest-v1-all @@ -97,7 +97,7 @@ services: ## Secure GraphQL routes - name: graphql-v1 - _comment: "PostgREST: /graphql/v1/* -> http://rest:3000/rpc/graphql" + _comment: 'PostgREST: /graphql/v1/* -> http://rest:3000/rpc/graphql' url: http://rest:3000/rpc/graphql routes: - name: graphql-v1-all @@ -111,7 +111,7 @@ services: hide_credentials: true - name: request-transformer config: - add: + add: headers: - Content-Profile:graphql_public - name: acl @@ -123,7 +123,7 @@ services: ## Secure Realtime routes - name: realtime-v1 - _comment: "Realtime: /realtime/v1/* -> ws://realtime:4000/socket/*" + _comment: 'Realtime: /realtime/v1/* -> ws://realtime:4000/socket/*' url: http://realtime-dev.supabase-realtime:4000/socket/ routes: - name: realtime-v1-all @@ -144,7 +144,7 @@ services: ## Storage routes: the storage server manages its own auth - name: storage-v1 - _comment: "Storage: /storage/v1/* -> http://storage:5000/*" + _comment: 'Storage: /storage/v1/* -> http://storage:5000/*' url: http://storage:5000/ routes: - name: storage-v1-all @@ -153,10 +153,18 @@ services: - /storage/v1/ plugins: - name: cors - + ## Analytics routes + - name: analytics-v1 + _comment: 'Analytics: /analytics/v1/* -> http://logflare:4000/*' + url: http://analytics:4000/ + routes: + - name: analytics-v1-all + strip_path: true + paths: + - /analytics/v1/ ## Secure Database routes - name: meta - _comment: "pg-meta: /pg/* -> http://pg-meta:8080/*" + _comment: 'pg-meta: /pg/* -> http://pg-meta:8080/*' url: http://meta:8080/ routes: - name: meta-all diff --git a/docker/volumes/db/logs.sql b/docker/volumes/db/logs.sql new file mode 100644 index 00000000000..22fc2479e4e --- /dev/null +++ b/docker/volumes/db/logs.sql @@ -0,0 +1,4 @@ +\set pguser `echo "$POSTGRES_USER"` + +create schema if not exists _analytics; +alter schema _analytics owner to :pguser; diff --git a/docker/volumes/logs/vector.yml b/docker/volumes/logs/vector.yml new file mode 100644 index 00000000000..2d7bd81764e --- /dev/null +++ b/docker/volumes/logs/vector.yml @@ -0,0 +1,201 @@ +api: + enabled: true + address: '0.0.0.0:9001' + +sources: + docker_syslog: + type: 'syslog' + address: '0.0.0.0:9000' + mode: 'tcp' + path: '/tmp/socket' + +transforms: + project_logs: + type: remap + inputs: + - docker_syslog + source: |- + .project = "default" + .event_message = del(.message) + del(.procid) + del(.source_id) + del(.source_type) + del(.facility) + del(.host) + del(.id) + router: + type: route + inputs: + - project_logs + route: + kong: '.appname == "kong"' + auth: '.appname == "auth"' + rest: '.appname == "rest"' + realtime: '.appname == "realtime"' + storage: '.appname == "storage"' + db: '.appname == "db"' + # Kong logs only include api requests + kong_logs: + type: remap + inputs: + - router.kong + source: |- + req, err = parse_nginx_log(.event_message, "combined") + if err == null { + .timestamp = req.timestamp + .metadata.request.headers.referer = req.referer + .metadata.request.headers.user_agent = req.agent + .metadata.request.headers.cf_connecting_ip = req.client + .metadata.request.method = req.method + .metadata.request.path = req.path + .metadata.request.protocol = req.protocol + .metadata.response.status_code = req.status + } + # TODO: create a separate page and filter for kong error logs + kong_err: + type: remap + inputs: + - router.kong + source: |- + .metadata.request.method = "GET" + .metadata.response.status_code = 200 + parsed, err = parse_nginx_log(.event_message, "error") + if err == null { + .timestamp = parsed.timestamp + .severity = parsed.severity + .metadata.request.host = parsed.host + .metadata.request.headers.cf_connecting_ip = parsed.client + url, err = split(parsed.request, " ") + if err == null { + .metadata.request.method = url[0] + .metadata.request.path = url[1] + .metadata.request.protocol = url[2] + } + } + # Gotrue logs are structured json strings which frontend parses directly. But we keep metadata for consistency. + auth_logs: + type: remap + inputs: + - router.auth + source: |- + parsed, err = parse_json(.event_message) + if err == null { + .metadata.timestamp = parsed.time + .metadata = merge!(.metadata, parsed) + } + # PostgREST logs are structured so we separate timestamp from message using regex + rest_logs: + type: remap + inputs: + - router.rest + source: |- + parsed, err = parse_regex(.event_message, r'^(?P
    -
    +
    -
    +
    { connectionString: project?.connectionString, }, { - enabled: entityType?.type === ENTITY_TYPE.VIEW, + enabled: + entityType?.type === ENTITY_TYPE.VIEW || entityType?.type === ENTITY_TYPE.MATERIALIZED_VIEW, } ) @@ -50,11 +51,15 @@ const TableDefinition = ({ id }: TableDefinitionProps) => { ) const { data: definition, isLoading } = - entityType?.type === ENTITY_TYPE.VIEW ? viewResult : tableResult + entityType?.type === ENTITY_TYPE.VIEW || entityType?.type === ENTITY_TYPE.MATERIALIZED_VIEW + ? viewResult + : tableResult const prepend = entityType?.type === ENTITY_TYPE.VIEW ? `create view ${entityType.schema}.${entityType.name} as\n` + : entityType?.type === ENTITY_TYPE.MATERIALIZED_VIEW + ? `create materialized view ${entityType.schema}.${entityType.name} as\n` : '' const formattedDefinition = useMemo( diff --git a/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx b/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx index f8186be45a4..c4909d2264e 100644 --- a/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx @@ -208,7 +208,8 @@ const TableGridEditor = ({ const tableId = selectedTable?.id - const isViewSelected = entityType?.type === ENTITY_TYPE.VIEW + const isViewSelected = + entityType?.type === ENTITY_TYPE.VIEW || entityType?.type === ENTITY_TYPE.MATERIALIZED_VIEW const isTableSelected = entityType?.type === ENTITY_TYPE.TABLE const isForeignTableSelected = entityType?.type === ENTITY_TYPE.FOREIGN_TABLE const isLocked = meta.excludedSchemas.includes(entityType?.schema ?? '') diff --git a/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx b/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx index 09e50969b71..3d3c15330ea 100644 --- a/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx +++ b/studio/components/layouts/TableEditorLayout/TableEditorLayout.tsx @@ -74,6 +74,8 @@ const TableEditorLayout = ({ if (entity?.type) { switch (entity.type) { case ENTITY_TYPE.MATERIALIZED_VIEW: + return meta.materializedViews.loadById(entity.id) + case ENTITY_TYPE.VIEW: return meta.views.loadById(entity.id) diff --git a/studio/components/ui/TwoOptionToggle.tsx b/studio/components/ui/TwoOptionToggle.tsx index d1255788b68..b7989042a0a 100644 --- a/studio/components/ui/TwoOptionToggle.tsx +++ b/studio/components/ui/TwoOptionToggle.tsx @@ -20,7 +20,7 @@ const TwoOptionToggle: FC = ({ isActive: boolean ) => `absolute top-0 z-1 text-xs inline-flex h-full items-center justify-center font-medium ${ - isActive ? 'hover:text-white' : 'hover:text-gray-600' + isActive ? 'hover:text-scale-1100 dark:hover:text-white' : 'hover:text-gray-600' } dark:hover:text-white focus:z-10 focus:outline-none focus:border-blue-300 focus:ring-blue transition ease-in-out duration-150` @@ -33,7 +33,7 @@ const TwoOptionToggle: FC = ({ style={{ width, translate: activeOption === options[1] ? '0px' : `${width - 2}px` }} aria-hidden="true" className={clsx( - 'z-0 inline-block rounded h-full dark:bg-scale-600 shadow transform', + 'z-0 inline-block rounded h-full bg-scale-200 dark:bg-scale-600 shadow transform', 'transition-all ease-in-out border border-scale-700' )} > @@ -42,14 +42,21 @@ const TwoOptionToggle: FC = ({ key={`toggle_${index}`} style={{ width: width + 1 }} className={` - ${activeOption === option ? 'text-scale-1200' : 'text-scale-1000'} - ${index === 0 ? 'right-0' : 'left-0'} + ${activeOption === option ? 'text-scale-1200' : 'text-scale-1000'} + ${index === 0 ? 'right-0' : 'left-0'} ${buttonStyle(activeOption === option)} cursor-pointer `} onClick={() => onClickOption(option)} > - {option} + + {option} + ))}
    diff --git a/studio/lib/vercelConfigs.ts b/studio/lib/vercelConfigs.ts index 5217cfc99f4..c59555650ed 100644 --- a/studio/lib/vercelConfigs.ts +++ b/studio/lib/vercelConfigs.ts @@ -64,6 +64,28 @@ export const VERCEL_INTEGRATION_CONFIGS = [ }, ], }, + { + id: 'nextjs-open-ai-doc-search', + name: 'Next.js OpenAI Doc Search Starter', + template: QuickStart.find((x) => x.title == 'OpenAI Vector Search'), + envs: [ + { + key: 'NEXT_PUBLIC_SUPABASE_URL', + alias: INTEGRATION_ENVS_ALIAS.ENDPOINT, + type: 'encrypted', + }, + { + key: 'NEXT_PUBLIC_SUPABASE_ANON_KEY', + alias: INTEGRATION_ENVS_ALIAS.ANONKEY, + type: 'encrypted', + }, + { + key: 'SUPABASE_SERVICE_ROLE_KEY', + alias: INTEGRATION_ENVS_ALIAS.SERVICEKEY, + type: 'encrypted', + }, + ], + }, { id: 'supabase-todo-list', name: 'Todo List Database', diff --git a/studio/package.json b/studio/package.json index 919e962cd5b..c2299cd3fef 100644 --- a/studio/package.json +++ b/studio/package.json @@ -108,7 +108,7 @@ "@storybook/addon-links": "^6.4.19", "@storybook/react": "^6.4.19", "@storybook/testing-library": "^0.0.9", - "@supabase/postgres-meta": "^0.60.7", + "@supabase/postgres-meta": "^0.62.0", "@tailwindcss/typography": "^0.5.2", "@testing-library/dom": "^8.19.0", "@testing-library/react": "^12.1.5", diff --git a/studio/pages/api/pg-meta/[ref]/materialized-views.ts b/studio/pages/api/pg-meta/[ref]/materialized-views.ts new file mode 100644 index 00000000000..ed47215e36f --- /dev/null +++ b/studio/pages/api/pg-meta/[ref]/materialized-views.ts @@ -0,0 +1,44 @@ +import { NextApiRequest, NextApiResponse } from 'next' + +import apiWrapper from 'lib/api/apiWrapper' +import { get } from 'lib/common/fetch' +import { constructHeaders } from 'lib/api/apiHelpers' +import { PG_META_URL } from 'lib/constants' + +export default (req: NextApiRequest, res: NextApiResponse) => + apiWrapper(req, res, handler, { withAuth: true }) + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { method } = req + + switch (method) { + case 'GET': + if (req.query.id) return handleGetOne(req, res) + return handleGetAll(req, res) + default: + res.setHeader('Allow', ['GET']) + res.status(405).json({ error: { message: `Method ${method} Not Allowed` } }) + } +} + +const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { + const headers = constructHeaders(req.headers) + let response = await get(`${PG_META_URL}/materialized-views`, { + headers, + }) + if (response.error) { + return res.status(400).json({ error: response.error }) + } + return res.status(200).json(response) +} + +const handleGetOne = async (req: NextApiRequest, res: NextApiResponse) => { + const headers = constructHeaders(req.headers) + let response = await get(`${PG_META_URL}/materialized-views/${req.query.id}`, { + headers, + }) + if (response.error) { + return res.status(400).json({ error: response.error }) + } + return res.status(200).json(response) +} diff --git a/studio/pages/api/pg-meta/[ref]/views.ts b/studio/pages/api/pg-meta/[ref]/views.ts index aa205be3019..3efbe270ff2 100644 --- a/studio/pages/api/pg-meta/[ref]/views.ts +++ b/studio/pages/api/pg-meta/[ref]/views.ts @@ -13,6 +13,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { switch (method) { case 'GET': + if (req.query.id) return handleGetOne(req, res) return handleGetAll(req, res) default: res.setHeader('Allow', ['GET']) @@ -30,3 +31,14 @@ const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { } return res.status(200).json(response) } + +const handleGetOne = async (req: NextApiRequest, res: NextApiResponse) => { + const headers = constructHeaders(req.headers) + let response = await get(`${PG_META_URL}/views/${req.query.id}`, { + headers, + }) + if (response.error) { + return res.status(400).json({ error: response.error }) + } + return res.status(200).json(response) +} diff --git a/studio/pages/project/[ref]/editor/[id].tsx b/studio/pages/project/[ref]/editor/[id].tsx index b59be8949b5..721f01a1f04 100644 --- a/studio/pages/project/[ref]/editor/[id].tsx +++ b/studio/pages/project/[ref]/editor/[id].tsx @@ -58,6 +58,7 @@ const TableEditorPage: NextPageWithLayout = () => { const tables: PostgresTable[] = meta.tables.list() const views: SchemaView[] = meta.views.list() + const materializedViews = meta.materializedViews.list() const foreignTables: Partial[] = meta.foreignTables.list() const selectedTable = !isNaN(Number(id)) @@ -66,6 +67,8 @@ const TableEditorPage: NextPageWithLayout = () => { // @ts-ignore .concat(views) // @ts-ignore + .concat(materializedViews) + // @ts-ignore .concat(foreignTables) .find((table) => table.id === Number(id)) : undefined diff --git a/studio/stores/pgmeta/MaterializedViewStore.ts b/studio/stores/pgmeta/MaterializedViewStore.ts new file mode 100644 index 00000000000..96d81b3ceb4 --- /dev/null +++ b/studio/stores/pgmeta/MaterializedViewStore.ts @@ -0,0 +1,39 @@ +import { PostgresMaterializedView } from '@supabase/postgres-meta' +import { ResponseError } from 'types' +import PostgresMetaInterface, { IPostgresMetaInterface } from '../common/PostgresMetaInterface' +import { IRootStore } from '../RootStore' +import { get } from 'lib/common/fetch' + +export interface IMaterializedViewStore extends IPostgresMetaInterface { + loadById: ( + id: number | string + ) => Promise | { error: ResponseError }> +} + +export default class MaterializedViewStore extends PostgresMetaInterface { + constructor( + rootStore: IRootStore, + dataUrl: string, + headers?: { + [prop: string]: any + }, + options?: { identifier: string } + ) { + super(rootStore, dataUrl, headers, options) + } + + async loadById(id: number | string) { + try { + const url = `${this.url}?id=${id}` + const response = await get(url, { headers: this.headers }) + if (response.error) throw response.error + + const data = response as Partial + // @ts-ignore + this.data[id] = data + return data + } catch (error: any) { + return { error } + } + } +} diff --git a/studio/stores/pgmeta/MetaStore.ts b/studio/stores/pgmeta/MetaStore.ts index b2b2267e2c8..88fe5e0c8da 100644 --- a/studio/stores/pgmeta/MetaStore.ts +++ b/studio/stores/pgmeta/MetaStore.ts @@ -44,6 +44,7 @@ import ExtensionsStore from './ExtensionsStore' import TypesStore from './TypesStore' import ForeignTableStore, { IForeignTableStore } from './ForeignTableStore' import ViewStore, { IViewStore } from './ViewStore' +import MaterializedViewStore, { IMaterializedViewStore } from './MaterializedViewStore' import { FOREIGN_KEY_DELETION_ACTION } from 'data/database/database-query-constants' const BATCH_SIZE = 1000 @@ -57,6 +58,7 @@ export interface IMetaStore { columns: IPostgresMetaInterface schemas: IPostgresMetaInterface views: IViewStore + materializedViews: IMaterializedViewStore foreignTables: IForeignTableStore hooks: IPostgresMetaInterface @@ -144,6 +146,7 @@ export default class MetaStore implements IMetaStore { columns: ColumnStore schemas: SchemaStore views: ViewStore + materializedViews: MaterializedViewStore foreignTables: ForeignTableStore hooks: HooksStore @@ -197,6 +200,11 @@ export default class MetaStore implements IMetaStore { this.columns = new ColumnStore(this.rootStore, `${this.baseUrl}/columns`, this.headers) this.schemas = new SchemaStore(this.rootStore, `${this.baseUrl}/schemas`, this.headers) this.views = new ViewStore(this.rootStore, `${this.baseUrl}/views`, this.headers) + this.materializedViews = new MaterializedViewStore( + this.rootStore, + `${this.baseUrl}/materialized-views`, + this.headers + ) this.foreignTables = new ForeignTableStore( this.rootStore, `${this.baseUrl}/foreign-tables`, @@ -950,6 +958,9 @@ export default class MetaStore implements IMetaStore { this.views.setUrl(`${this.baseUrl}/views`) this.views.setHeaders(this.headers) + this.materializedViews.setUrl(`${this.baseUrl}/materialized-views`) + this.materializedViews.setHeaders(this.headers) + this.foreignTables.setUrl(`${this.baseUrl}/foreign-tables`) this.foreignTables.setHeaders(this.headers)