-
Getting Started
+
- Discover how to set up a database to an app making queries in just a few minutes.
+
-
What you will learn
-
-
-
-
-
-
-
Setting up a Supabase project and app
-
-
-
-
-
-
Adding some sample data to your project
-
-
-
-
-
-
Select() from your new database table
+
+
+ {/*
+
+ */}
+ ## Client Libraries
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ ## Migrate to Supabase
+
+
+ Bring your existing data, auth and storage to Supabase following our migration guides.
+
+
+
+
+
+
+ )
+ })}
+
+
+
+
+
+
+
+
+
+
+ ### Additional resources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ### Self-Hosting
+
+ Get started with self-hosting Supabase.
+
+
+
+
+
+
+
-
-
-

-

-
-
Ready to make a fully working app?
-
Follow the step-by-step tutorials.
-
-
-
-
-
-
-
-
- ### Product Documentation
-
-
-
-
-
-
-
-
-
-
-
- ### Reference Documentation
-
-
-
-
-
-
-{/* end of container */}
export const products = [
{
title: 'Database',
- icon: '/docs/img/icons/menu/database',
+ icon: 'database',
hasLightIcon: true,
href: '/guides/database',
description:
@@ -227,16 +202,16 @@ export const products = [
},
{
title: 'Auth',
- icon: '/docs/img/icons/menu/auth',
+ icon: 'auth',
hasLightIcon: true,
- href: '/guides/auth/overview',
+ href: '/guides/auth',
description:
'Add and manage email and password, passwordless, OAuth, and mobile logins to your project through a suite of identity providers and APIs.',
span: 'col-span-12 md:col-span-6',
},
{
title: 'Storage',
- icon: '/docs/img/icons/menu/storage',
+ icon: 'storage',
hasLightIcon: true,
href: '/guides/storage',
description:
@@ -244,7 +219,7 @@ export const products = [
},
{
title: 'Realtime',
- icon: '/docs/img/icons/menu/realtime',
+ icon: 'realtime',
hasLightIcon: true,
href: '/guides/realtime',
description:
@@ -252,7 +227,7 @@ export const products = [
},
{
title: 'Edge Functions',
- icon: '/docs/img/icons/menu/functions',
+ icon: 'functions',
hasLightIcon: true,
href: '/guides/functions',
description:
@@ -260,105 +235,122 @@ export const products = [
},
]
-export const apis = [
+export const migrationGuides = [
{
- title: 'JavaScript Client Library',
- icon: '/docs/img/icons/menu/reference-javascript',
- hasLightIcon: true,
+ title: 'Firebase Auth',
+ icon: '/docs/img/icons/firebase-icon',
+ href: '/guides/resources/migrating-to-supabase/firebase-auth',
+ },
+ {
+ title: 'Firestore Data',
+ icon: '/docs/img/icons/firebase-icon',
+ href: '/guides/resources/migrating-to-supabase/firestore-data',
+ },
+ {
+ title: 'Firebase Storage',
+ icon: '/docs/img/icons/firebase-icon',
+ href: '/guides/resources/migrating-to-supabase/firebase-storage',
+ },
+ {
+ title: 'Heroku',
+ icon: '/docs/img/icons/heroku-icon',
+ href: '/guides/resources/migrating-to-supabase/heroku',
+ },
+ {
+ title: 'Render',
+ icon: '/docs/img/icons/render-icon',
+ href: '/guides/resources/migrating-to-supabase/render',
+ },
+ {
+ title: 'Amazon RDS',
+ icon: '/docs/img/icons/aws-rds-icon',
+ href: '/guides/resources/migrating-to-supabase/amazon-rds',
+ },
+]
+
+export const selfHostingOptions = [
+ {
+ title: 'Auth',
+ icon: 'auth',
+ href: 'reference/self-hosting-auth/introduction',
+ },
+ {
+ title: 'Realtime',
+ icon: 'realtime',
+ href: 'reference/self-hosting-realtime/introduction',
+ },
+ {
+ title: 'Storage',
+ icon: 'storage',
+ href: 'reference/self-hosting-storage/introduction',
+ },
+ {
+ title: 'Analytics',
+ icon: 'analytics',
+ href: 'reference/self-hosting-analytics/introduction',
+ },
+]
+
+export const clientLibraries = [
+ {
+ title: 'Javascript',
+ icon: 'reference-javascript',
href: 'reference/javascript/introduction',
- description: 'Official client libraries for JavaScript and TypeScript.',
- span: 'col-span-12 md:col-span-6',
},
{
- title: 'Flutter Client Library',
- icon: '/docs/img/icons/menu/reference-dart',
- hasLightIcon: true,
+ title: 'Flutter',
+ icon: 'reference-dart',
href: 'reference/dart/introduction',
- description: 'Official client libraries for Flutter and Dart.',
- span: 'col-span-12 md:col-span-6',
},
+ {
+ title: 'Python',
+ icon: 'reference-python',
+ href: 'reference/python/introduction',
+ },
+ {
+ title: 'C#',
+ icon: 'reference-csharp',
+ href: 'reference/csharp/introduction',
+ },
+ {
+ title: 'Swift',
+ icon: 'reference-swift',
+ href: 'reference/swift/introduction',
+ },
+ {
+ title: 'Kotlin',
+ icon: 'reference-kotlin',
+ href: 'reference/kotlin/introduction',
+ },
+]
+
+export const additionalResources = [
{
title: 'Management API',
- icon: '/docs/img/icons/menu/reference-api',
- hasLightIcon: true,
+ description: 'Manage your Supabase projects and organizations.',
+ icon: 'reference-api',
href: 'reference/api/introduction',
- description: 'Manage your Supabase projects and organizations programmatically.',
- span: 'col-span-12 md:col-span-6',
},
{
title: 'Supabase CLI',
- icon: '/docs/img/icons/menu/reference-api',
- hasLightIcon: true,
+ description: 'Use the CLI to develop, manage and deploy your projects.',
+ icon: 'reference-cli',
href: 'reference/cli/introduction',
- description:
- 'Use the CLI to develop your project locally, manage database migrations, and deploy to the Supabase Platform or self-host.',
- span: 'col-span-12 md:col-span-6',
},
{
- title: 'Self-Hosting Auth',
- icon: '/docs/img/icons/menu/reference-auth',
- hasLightIcon: true,
- href: 'reference/self-hosting-auth/introduction',
- description: 'Self-host your own Auth server.',
- },
- {
- title: 'Self-Hosting Storage',
- icon: '/docs/img/icons/menu/reference-storage',
- hasLightIcon: true,
- href: 'reference/self-hosting-storage/introduction',
- description: 'Self-host your own Storage server.',
- },
- {
- title: 'Self-Hosting Realtime',
- icon: '/docs/img/icons/menu/reference-realtime',
- hasLightIcon: true,
- 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 = [
- {
- // header: '/docs/img/cards/sample-card-header-3.svg',
title: 'Platform Guides',
- icon: '/docs/img/icons/menu/platform',
- hasLightIcon: true,
+ description: 'Learn more about the tools and services powering Supabase.',
+ icon: 'platform',
href: 'guides/platform',
- description:
- 'Learn more about the tools and services powering the Supabase Platform, available add-ons, and how to customize the platform for your needs.',
},
{
- // header: '/docs/img/cards/sample-card-header-2.svg',
- title: 'Resources',
- icon: '/docs/img/icons/menu/resources',
- hasLightIcon: true,
- href: 'guides/resources',
- description: 'View examples, curated content from the community, migration guides, and more.',
- },
- {
- title: 'Self-Hosting Supabase',
- icon: '/docs/img/icons/menu/platform',
- hasLightIcon: true,
- href: 'guides/self-hosting',
- description: 'Learn how to self-host Supabase and deploy to your own infrastructure.',
- },
- {
- // header: '/docs/img/cards/sample-card-header-2.svg',
title: 'Integrations',
- icon: '/docs/img/icons/menu/integrations',
- hasLightIcon: true,
- href: 'guides/integrations',
description: 'Explore a variety of integrations from Supabase partners.',
+ icon: 'integrations',
+ href: 'guides/integrations',
},
]
-export const Page = ({ children }) =>
+export const Page = ({ children }) =>
export default Page
diff --git a/apps/docs/pages/reference/cli/config.tsx b/apps/docs/pages/reference/cli/config.tsx
index 6a167f1ec03..096aa30b6aa 100644
--- a/apps/docs/pages/reference/cli/config.tsx
+++ b/apps/docs/pages/reference/cli/config.tsx
@@ -25,7 +25,7 @@ export default function Config() {
Supabase CLI config
-
+
CLI configuration
diff --git a/apps/docs/pages/reference/dart/v0/[...slug].tsx b/apps/docs/pages/reference/dart/v0/[...slug].tsx
index 8cee39483e0..86af70c75c8 100644
--- a/apps/docs/pages/reference/dart/v0/[...slug].tsx
+++ b/apps/docs/pages/reference/dart/v0/[...slug].tsx
@@ -9,7 +9,15 @@ const sections = flattenSections(clientLibsCommonSections)
const libraryPath = '/dart/v0'
export default function JSReference(props) {
- return
+ return (
+
+ )
}
export async function getStaticProps() {
diff --git a/apps/docs/pages/reference/javascript/v1/[...slug].tsx b/apps/docs/pages/reference/javascript/v1/[...slug].tsx
index 95a578fc559..3fb14627bfe 100644
--- a/apps/docs/pages/reference/javascript/v1/[...slug].tsx
+++ b/apps/docs/pages/reference/javascript/v1/[...slug].tsx
@@ -1,4 +1,3 @@
-import { inspect } from 'util'
import clientLibsCommonSections from '~/../../spec/common-client-libs-sections.json'
import typeSpec from '~/../../spec/enrichments/tsdoc_v1/combined.json'
import spec from '~/../../spec/supabase_js_v1.yml' assert { type: 'yml' }
@@ -18,6 +17,7 @@ export default function JSReference(props) {
typeSpec={typeSpec}
pageProps={props}
type="client-lib"
+ isOldVersion
/>
)
}
diff --git a/apps/docs/public/img/guides/database/og-vault.png b/apps/docs/public/img/guides/database/og-vault.png
new file mode 100644
index 00000000000..71ff756fef8
Binary files /dev/null and b/apps/docs/public/img/guides/database/og-vault.png differ
diff --git a/apps/docs/public/img/guides/database/vault-decrypted-data.png b/apps/docs/public/img/guides/database/vault-decrypted-data.png
new file mode 100644
index 00000000000..e802f9d1ef3
Binary files /dev/null and b/apps/docs/public/img/guides/database/vault-decrypted-data.png differ
diff --git a/apps/docs/public/img/guides/database/vault-encrypted-data.png b/apps/docs/public/img/guides/database/vault-encrypted-data.png
new file mode 100644
index 00000000000..631536c56fa
Binary files /dev/null and b/apps/docs/public/img/guides/database/vault-encrypted-data.png differ
diff --git a/apps/docs/public/img/guides/database/vault-encrypting-columns.png b/apps/docs/public/img/guides/database/vault-encrypting-columns.png
new file mode 100644
index 00000000000..2a2c487eaa7
Binary files /dev/null and b/apps/docs/public/img/guides/database/vault-encrypting-columns.png differ
diff --git a/apps/docs/public/img/guides/database/vault-hello-compressed.mp4 b/apps/docs/public/img/guides/database/vault-hello-compressed.mp4
new file mode 100644
index 00000000000..48395586435
Binary files /dev/null and b/apps/docs/public/img/guides/database/vault-hello-compressed.mp4 differ
diff --git a/apps/docs/public/img/guides/integrations/cloudflare-integration/2.png b/apps/docs/public/img/guides/integrations/cloudflare-integration/2.png
new file mode 100644
index 00000000000..4348e508742
Binary files /dev/null and b/apps/docs/public/img/guides/integrations/cloudflare-integration/2.png differ
diff --git a/apps/docs/public/img/guides/integrations/cloudflare-integration/3.png b/apps/docs/public/img/guides/integrations/cloudflare-integration/3.png
new file mode 100644
index 00000000000..2a6db2dc104
Binary files /dev/null and b/apps/docs/public/img/guides/integrations/cloudflare-integration/3.png differ
diff --git a/apps/docs/public/img/guides/integrations/cloudflare-integration/4.png b/apps/docs/public/img/guides/integrations/cloudflare-integration/4.png
new file mode 100644
index 00000000000..d1aba0bb706
Binary files /dev/null and b/apps/docs/public/img/guides/integrations/cloudflare-integration/4.png differ
diff --git a/apps/docs/public/img/guides/integrations/cloudflare-integration/5.png b/apps/docs/public/img/guides/integrations/cloudflare-integration/5.png
new file mode 100644
index 00000000000..1db9444f210
Binary files /dev/null and b/apps/docs/public/img/guides/integrations/cloudflare-integration/5.png differ
diff --git a/apps/docs/public/img/guides/integrations/cloudflare-integration/6.png b/apps/docs/public/img/guides/integrations/cloudflare-integration/6.png
new file mode 100644
index 00000000000..0c7ce386ac6
Binary files /dev/null and b/apps/docs/public/img/guides/integrations/cloudflare-integration/6.png differ
diff --git a/apps/docs/public/img/guides/integrations/cloudflare-integration/7.png b/apps/docs/public/img/guides/integrations/cloudflare-integration/7.png
new file mode 100644
index 00000000000..f6d4c49df5f
Binary files /dev/null and b/apps/docs/public/img/guides/integrations/cloudflare-integration/7.png differ
diff --git a/apps/docs/public/img/icons/menu/database.svg b/apps/docs/public/img/icons/menu/database.svg
index 8b8205599c2..80d0254d7e8 100644
--- a/apps/docs/public/img/icons/menu/database.svg
+++ b/apps/docs/public/img/icons/menu/database.svg
@@ -1,5 +1,5 @@
diff --git a/apps/docs/public/img/integrations/logos/cloudflare_workers_logo.jpeg b/apps/docs/public/img/integrations/logos/cloudflare_workers_logo.jpeg
new file mode 100644
index 00000000000..6d09d84acc5
Binary files /dev/null and b/apps/docs/public/img/integrations/logos/cloudflare_workers_logo.jpeg differ
diff --git a/apps/docs/public/img/integrations/logos/cloudflare_workers_logo.png b/apps/docs/public/img/integrations/logos/cloudflare_workers_logo.png
new file mode 100644
index 00000000000..af9e1b1a658
Binary files /dev/null and b/apps/docs/public/img/integrations/logos/cloudflare_workers_logo.png differ
diff --git a/apps/docs/public/img/integrations/logos/upstash_redis_logo.jpeg b/apps/docs/public/img/integrations/logos/upstash_redis_logo.jpeg
new file mode 100644
index 00000000000..846d395ccf3
Binary files /dev/null and b/apps/docs/public/img/integrations/logos/upstash_redis_logo.jpeg differ
diff --git a/apps/docs/public/img/integrations/logos/upstash_redis_logo.png b/apps/docs/public/img/integrations/logos/upstash_redis_logo.png
new file mode 100644
index 00000000000..eb0df80afd9
Binary files /dev/null and b/apps/docs/public/img/integrations/logos/upstash_redis_logo.png differ
diff --git a/apps/docs/public/sitemap.xml b/apps/docs/public/sitemap.xml
index 78ce4d811b9..c3d36c42b96 100644
--- a/apps/docs/public/sitemap.xml
+++ b/apps/docs/public/sitemap.xml
@@ -552,6 +552,12 @@
0.5
+
+ https://supabase.com/docs/guides/integrations/cloudflare-workers
+ weekly
+ 0.5
+
+
https://supabase.com/docs/guides/integrations/dhiwise
weekly
diff --git a/apps/docs/scripts/search/sources/markdown.ts b/apps/docs/scripts/search/sources/markdown.ts
index 851839a772b..91beb6cc01a 100644
--- a/apps/docs/scripts/search/sources/markdown.ts
+++ b/apps/docs/scripts/search/sources/markdown.ts
@@ -96,6 +96,23 @@ export function splitTreeBy(tree: Root, predicate: (node: Content) => boolean) {
}, [])
}
+/**
+ * Parses a markdown heading which can optionally
+ * contain a custom anchor in the format:
+ *
+ * ```markdown
+ * ### My Heading [#my-custom-anchor]
+ * ```
+ */
+export function parseHeading(heading: string): { heading: string; customAnchor?: string } {
+ const match = heading.match(/(.*) *\[#(.*)\]/)
+ if (match) {
+ const [, heading, customAnchor] = match
+ return { heading, customAnchor }
+ }
+ return { heading }
+}
+
/**
* Processes MDX content for search indexing.
* It extracts metadata, strips it of all JSX,
@@ -140,12 +157,20 @@ export function processMdxForSearch(content: string): ProcessedMdx {
const sections = sectionTrees.map((tree) => {
const [firstNode] = tree.children
+ const content = toMarkdown(tree)
- const heading = firstNode.type === 'heading' ? toString(firstNode) : undefined
- const slug = heading ? slugger.slug(heading) : undefined
+ const rawHeading: string = firstNode.type === 'heading' ? toString(firstNode) : undefined
+
+ if (!rawHeading) {
+ return { content }
+ }
+
+ const { heading, customAnchor } = parseHeading(rawHeading)
+
+ const slug = slugger.slug(customAnchor ?? heading)
return {
- content: toMarkdown(tree),
+ content,
heading,
slug,
}
diff --git a/apps/docs/styles/main.scss b/apps/docs/styles/main.scss
index 1c60d4f3100..08c65126db1 100644
--- a/apps/docs/styles/main.scss
+++ b/apps/docs/styles/main.scss
@@ -80,6 +80,10 @@ article h1 {
@apply text-scale-900 text-xs;
}
+.prose :where(p):not(:where([class~='not-prose'] *)) {
+ white-space: pre-line;
+}
+
code[class*='language-'],
pre[class*='language-'] {
text-shadow: none !important;
diff --git a/apps/www/_blog/2020-08-02-supabase-alpha-july-2020.mdx b/apps/www/_blog/2020-08-02-supabase-alpha-july-2020.mdx
index e93c38d00f3..3d450ab4765 100644
--- a/apps/www/_blog/2020-08-02-supabase-alpha-july-2020.mdx
+++ b/apps/www/_blog/2020-08-02-supabase-alpha-july-2020.mdx
@@ -40,7 +40,7 @@ We've made some massive improvements to our Table Editor that we're excited to s
Last month we made it easy to drill into your table relationships. This month, we make it possible to drill multiple levels deep.
-
+
+## Serving the manifest file
+
+The `ai-plugin.json` [manifest file](https://platform.openai.com/docs/plugins/getting-started/plugin-manifest) is required for ChatGPT to identify our plugin, know what kind of authentication mechanism is used, understand where to find the OpenAPI definition, and some other details about our plugin. You can find the full list of supported parameters in the [OpenAI docs](https://platform.openai.com/docs/plugins/getting-started/plugin-manifest).
+
+Supabase Edge Runtime does currently not support hosting/serving of static files, however, we can import JSON files in our function and serve them as a JSON response. As this needs to be at the root of our domain, we add this to our [main function handler](https://github.com/supabase-community/chatgpt-plugin-template-deno/blob/main/functions/main/index.ts#L69-L74):
+
+```ts
+// functions/main/index.ts
+import aiPlugins from './ai-plugins.json' assert { type: 'json' }
+
+// [...]
+
+// Serve /.well-known/ai-plugin.json
+if (service_name === '.well-known') {
+ return new Response(JSON.stringify(aiPlugins), {
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
+ })
+}
+```
+
+Now, when running Edge Runtime locally via Docker, our plugin manifest will be available at [http://localhost:8000/.well-known/ai-plugin.json](http://localhost:8000/.well-known/ai-plugin.json)
+
+## Generating the OpenAPI definition with swagger-jsdoc
+
+The [OpenAPI definition](https://platform.openai.com/docs/plugins/getting-started/openapi-definition) is required for ChatGPT to know how to underact with our API. Only endpoints included in there will be exposed to ChatGPT, which allows you to selectively make our endpoints available, or add specific endpoints for ChatGPT.
+
+The OpenAPI definition can be either in YAML or JSON format. We’ll be using JSON and the same approach as above to serve it. Writing an OpenAPI definition is not something we will want to do by hand, luckily there is an open source tool called [swagger-jsdoc](https://github.com/Surnet/swagger-jsdoc) which we can use to annotate our endpoints with JSDoc comments and generate the OpenAPI definition with a [little script](https://github.com/supabase-community/chatgpt-plugin-template-deno/blob/main/scripts/generate-openapi-spec.ts).
+
+```ts
+// /scripts/generate-openapi-spec.ts
+import swaggerJsdoc from 'npm:swagger-jsdoc@6.2.8'
+
+const options = {
+ definition: {
+ openapi: '3.0.1',
+ info: {
+ title: 'TODO Plugin',
+ description: `A plugin that allows the user to create and manage a TODO list using ChatGPT. If you do not know the user's username, ask them first before making queries to the plugin. Otherwise, use the username "global".`,
+ version: '1.0.0',
+ },
+ servers: [{ url: 'http://localhost:8000' }],
+ },
+ apis: ['./functions/chatgpt-plugin/index.ts'], // files containing annotations as above
+}
+
+const openapiSpecification = swaggerJsdoc(options)
+const openapiString = JSON.stringify(openapiSpecification, null, 2)
+const encoder = new TextEncoder()
+const data = encoder.encode(openapiString)
+await Deno.writeFile('./functions/chatgpt-plugin/openapi.json', data)
+console.log(openapiString)
+```
+
+Since this script is run outside of the function execution, e.g. as a GitHub Action, we can use [npm specifiers](https://deno.com/manual/node/npm_specifiers) to import `swagger-jsdoc`.
+
+Next, we create our `/functions/chatgpt-plugin/index.ts` [file](https://github.com/supabase-community/chatgpt-plugin-template-deno/blob/main/functions/chatgpt-plugin/index.ts) where we use the [Deno oak router](https://deno.land/x/oak) to build our API and annotate it with JSDOC comments.
+
+```ts
+// /functions/chatgpt-plugin/index.ts
+import { Application, Router } from 'https://deno.land/x/oak@v11.1.0/mod.ts'
+import openapi from './openapi.json' assert { type: 'json' }
+
+console.log('Hello from `chatgpt-plugin` Function!')
+
+const _TODOS: { [key: string]: Array
} = {
+ user: ['Build your own ChatGPT Plugin!'],
+}
+
+/**
+ * @openapi
+ * components:
+ * schemas:
+ * getTodosResponse:
+ * type: object
+ * properties:
+ * todos:
+ * type: array
+ * items:
+ * type: string
+ * description: The list of todos.
+ */
+
+const router = new Router()
+router
+ .get('/chatgpt-plugin', (ctx) => {
+ ctx.response.body = 'Building ChatGPT plugins with Deno!'
+ })
+ /**
+ * @openapi
+ * /chatgpt-plugin/todos/{username}:
+ * get:
+ * operationId: getTodos
+ * summary: Get the list of todos
+ * parameters:
+ * - in: path
+ * name: username
+ * schema:
+ * type: string
+ * required: true
+ * description: The name of the user.
+ * responses:
+ * 200:
+ * description: OK
+ * content:
+ * application/json:
+ * schema:
+ * $ref: '#/components/schemas/getTodosResponse'
+ */
+ .get('/chatgpt-plugin/todos/:username', (ctx) => {
+ const username = ctx.params.username.toLowerCase()
+ ctx.response.body = _TODOS[username] ?? []
+ })
+ .get('/chatgpt-plugin/openapi.json', (ctx) => {
+ ctx.response.body = JSON.stringify(openapi)
+ ctx.response.headers.set('Content-Type', 'application/json')
+ })
+
+const app = new Application()
+app.use(router.routes())
+app.use(router.allowedMethods())
+
+await app.listen({ port: 8000 })
+```
+
+With our JSDoc annotation in place, we can now run the generation script in the terminal:
+
+```bash
+deno run --allow-read --allow-write scripts/generate-openapi-spec.ts
+```
+
+## Adding the CORS headers
+
+Lastly, we need to add some CORS headers to make the browser happy. We define them in a `/functions/_shared/cors.ts` [file](https://github.com/supabase-community/chatgpt-plugin-template-deno/blob/main/functions/_shared/cors.ts) so we can easily reuse them across our `main` and `chatgpt-plugins` function.
+
+```tsx
+// /functions/_shared/cors.ts
+export const corsHeaders = {
+ 'Access-Control-Allow-Origin': 'https://chat.openai.com',
+ 'Access-Control-Allow-Credentials': 'true',
+ 'Access-Control-Allow-Private-Network': 'true',
+ 'Access-Control-Allow-Headers': '*',
+}
+```
+
+Now we can easily add them to all our `chatgpt-plugin` routes a middleware for our oak application.
+
+```ts
+// /functions/chatgpt-plugin/index.ts
+import { Application, Router } from 'https://deno.land/x/oak@v11.1.0/mod.ts'
+import { corsHeaders } from '../_shared/cors.ts'
+
+// [...]
+const app = new Application()
+// ChatGPT specific CORS headers
+app.use(async (ctx, next) => {
+ await next()
+ let key: keyof typeof corsHeaders
+ for (key in corsHeaders) {
+ ctx.response.headers.set(key, corsHeaders[key])
+ }
+})
+app.use(router.routes())
+app.use(router.allowedMethods())
+
+await app.listen({ port: 8000 })
+```
+
+## Running locally with Docker
+
+Now that we’ve got all the pieces in place, let’s spin up Edge Runtime locally and test things out. For this, we need a [Dockerfile](https://github.com/supabase-community/chatgpt-plugin-template-deno/blob/main/Dockerfile) and for convenience, we can add a [docker-compose file](https://github.com/supabase-community/chatgpt-plugin-template-deno/blob/main/docker-compose.yml) also.
+
+```txt
+// Dockerfile
+FROM ghcr.io/supabase/edge-runtime:v1.2.18
+
+COPY ./functions /home/deno/functions
+CMD [ "start", "--main-service", "/home/deno/functions/main" ]
+```
+
+This will pull down Edge Runtime v1.2.18 (you can check the l[atest release here](https://github.com/supabase/edge-runtime/releases)) and start up the main service (our `/functions/main/index.ts` function).
+
+```yaml
+// docker-compose.yml
+version: "3.9"
+services:
+ web:
+ build: .
+ volumes:
+ - type: bind
+ source: ./functions
+ target: /home/deno/functions
+ ports:
+ - "8000:9000"
+```
+
+Edge Runtime will serve requests on port `9000`, so we’re creating a mapping from `[localhost:8000](http://localhost:8000)` where we want to serve our requests locally (of course you can adapt this to your needs) to port `9000` of our Docker container.
+
+Furthermore, we’re using [bind mounts](https://docs.docker.com/storage/bind-mounts/) to mount our functions directory into the container. This allows us to make modifications to our functions without needing to rebuild the container after, making for a great local developer experience.
+
+That’s it, now we can build and spin up our container from the terminal:
+
+```bash
+docker compose up --build
+```
+
+Go ahead and try it out by visiting:
+
+- [http://localhost:8000/chatgpt-plugin](http://localhost:8000/chatgpt-plugin)
+- [http://localhost:8000/.well-known/ai-plugin.json](http://localhost:8000/.well-known/ai-plugin.json)
+- [http://localhost:8000/chatgpt-plugin/openapi.json](http://localhost:8000/chatgpt-plugin/openapi.json)
+- [http://localhost:8000/chatgpt-plugin/todos/user](http://localhost:8000/chatgpt-plugin/todos/user)
+
+## Installing and testing the plugin locally
+
+You can conveniently test your plugin while running it on [localhost](http://localhost) using the [ChatGPT UI](https://chat.openai.com/):
+
+1. Select the plugin model from the top drop down, then select “Plugins”, “Plugin Store”, and finally “Develop your own plugin”.
+2. Enter `localhost:8000` and click "Find manifest file".
+3. Confirm with “Install [localhost](http://localhost) plugin”.
+
+That’s it, now go ahead and ask some questions, e.g. you can start with “Do I have any todos?”
+
+
+
+There you are, now go ahead and build your own plugin as it says on your todo list ;)
+
+## Deploying to Fly.io
+
+Once you’re happy with the functionality of your plugin, go ahead and deploy it to [Fly.io](http://Fly.io). After installing the [flyctl cli](https://fly.io/docs/hands-on/install-flyctl/), it only takes a couple of steps:
+
+- Change `http://localhost:8000` to your Fly domain in the `/main/ai-plugins.json` file
+- Open `fly.toml` and update the app name and optionally the region etc.
+- In your terminal, run `fly apps create` and specify the app name you just set in your `fly.toml` file.
+- Finally, run `fly deploy`.
+
+There you go, now you’re ready to [release your plugin to the world](https://platform.openai.com/docs/plugins/production/faq) \o/
+
+## Conclusion
+
+ChatGPT is a powerful new interface and its usage is growing rapidly. With ChatGPT Plugins you can allow your users to access your service directly from ChatGPT, using cutting edge technologies like TypeScript and Deno.
+
+In a next step you can add authentication to your plugin, let us know on [Twitter](https://twitter.com/Supabase) if you’d be interested in a tutorial for that. We can’t wait to see what you will build!
+
+## More AI resources
+
+- [OpenAI ChatGPT Plugin docs](https://platform.openai.com/docs/plugins/introduction)
+- [Advanced plugin template](https://github.com/openai/chatgpt-retrieval-plugin)
+- Learn to build your own ChatGPT-style docs search with [Deno Fresh](https://deno.com/blog/build-chatgpt-doc-search-with-supabase-fresh) or [Next.js](https://vercel.com/templates/next.js/nextjs-openai-doc-search-starter)
+- [How we builtChatGPT for the Supabase Docs](https://supabase.com/blog/chatgpt-supabase-docs).
+- [Docs pgvector: Embeddings and vector similarity](https://supabase.com/docs/guides/database/extensions/pgvector)
diff --git a/apps/www/_customers/epsilon3.mdx b/apps/www/_customers/epsilon3.mdx
index 6c6c94f7ac4..1cfba9ea83d 100644
--- a/apps/www/_customers/epsilon3.mdx
+++ b/apps/www/_customers/epsilon3.mdx
@@ -1,7 +1,11 @@
---
-title: Epsilon3 digitize paper-based procedures in the space industry using telemetry data, simplifying testing and operations.
name: Epsilon3
+title: Epsilon3 digitize paper-based procedures in the space industry using telemetry data, simplifying testing and operations.
+# Use meta_title to add a custom meta title. Otherwise it defaults to '{name} | Supabase Customer Stories':
+# meta_title:
description: Epsilon3 uses Supabase to help teams execute secure and reliable operations in an industry where project spend runs into the billions.
+# Use meta_description to add a custom meta description. Otherwise it defaults to {description}:
+meta_description: Epsilon3 uses Supabase to help teams execute secure and reliable operations in an industry where project spend runs into the billions.
author: rory_wilding
author_title: Supabase
author_url: https://github.com/kiwicopple
diff --git a/apps/www/_customers/happyteams.mdx b/apps/www/_customers/happyteams.mdx
index 9a36c5ba610..78f713bd753 100644
--- a/apps/www/_customers/happyteams.mdx
+++ b/apps/www/_customers/happyteams.mdx
@@ -1,7 +1,11 @@
---
-title: HappyTeams unlocks better performance and reduces cost with Supabase.
name: HappyTeams
+title: HappyTeams unlocks better performance and reduces cost with Supabase.
+# Use meta_title to add a custom meta title. Otherwise it defaults to '{name} | Supabase Customer Stories':
+# meta_title:
description: How a bootstrapped startup migrated from Heroku to Supabase in 30 minutes and never looked back.
+# Use meta_description to add a custom meta description. Otherwise it defaults to {description}:
+meta_description: How a bootstrapped startup migrated from Heroku to Supabase in 30 minutes and never looked back.
author: rory_wilding
author_title: Supabase
author_url: https://github.com/kiwicopple
diff --git a/apps/www/_customers/markprompt.mdx b/apps/www/_customers/markprompt.mdx
new file mode 100644
index 00000000000..200ae34564d
--- /dev/null
+++ b/apps/www/_customers/markprompt.mdx
@@ -0,0 +1,81 @@
+---
+title: Markprompt and Supabase - GDPR-Compliant AI Chatbots for Docs and Websites.
+name: Markprompt
+description: AI-powered chatbot platform, Markprompt, empowers developers to deliver efficient and GDPR-compliant prompt experiences on top of their content, by leveraging Supabase's secure and privacy-focused database and authentication solutions.
+author: paul_copplestone
+author_title: Supabase
+author_url: https://github.com/kiwicopple
+author_image_url: https://avatars2.githubusercontent.com/u/10214025?s=400&u=c6775be2ae667e2acae3ccd347fed62bb3f5b3e7&v=4
+authorURL: https://github.com/kiwicopple
+logo: /images/customers/logos/markprompt.png
+logo_inverse: /images/customers/logos/light/markprompt.png
+og_image: /images/customers/og/markprompt.png
+tags:
+ - supabase
+date: '2023-05-17'
+company_url: 'https://markprompt.com/'
+stats:
+ [
+ { stat: '00,000', label: Example stat },
+ { stat: '00,000', label: Example stat },
+ { stat: '00,000', label: Example stat },
+ ]
+misc: [{ label: 'Built by', text: 'The Motif Team' }]
+about: Enterprise-grade AI chatbots for your website and docs.
+---
+
+Markprompt is an AI-powered chatbot platform that simplifies the process of finding relevant information in large-scale knowledge bases. With its chat-based interface, users can ask questions in natural language and receive instant, accurate answers. Markprompt's advanced analytics help teams identify knowledge gaps and improve their documentation strategies.
+
+
+ We’ve found that having access to the full features of Postgres, colocated with the embeddings,
+ makes it the perfect vector database.
+
+
+## The Challenge
+
+When developing the platform, Markprompt considered GDPR compliance crucial as it enabled them to handle user data responsibly, maintain data privacy, and foster trust and confidence among their customers. As a result, the choice of a robust database and authentication solution became vital in ensuring adherence to GDPR requirements.
+
+Markprompt has successfully indexed over half a million sections of content, with a steady influx of 10,000 - 50,000 new sections daily. This emphasizes the significance of their vector database solution in terms of reliability and scalability.
+
+
+ We decided to use Supabase over other specialized vector databases because it enabled us to be
+ GDPR compliant from day one with little effort.
+
+
+## Why they chose Supabase:
+
+By utilizing a trusted solution like Supabase for their database and authentication needs, Markprompt can implement access controls, user consent management, and audit trails, which are essential elements for GDPR compliance. This ensures that personal data is handled with care, protected from unauthorized access, and enables Markprompt to meet the strict data protection standards set forth by the GDPR.
+
+Markprompt were also impressed with the ease of use and scalability of Supabase. The platform allowed them to focus on building their product, knowing that they had a powerful and reliable database solution backing them up.
+
+
+ Supabase has been a joy to work with. Handling enterprise data at scale can be daunting, but we
+ feel we are in good hands as we grow.
+
+
+## What They Built
+
+Markprompt is a set of API endpoints that allow you to train your content and create a prompt to ask questions to it, for instance for a docs site. Markprompt also designed a user-friendly web dashboard that simplifies content training, synchronization with GitHub repos or websites, access key management, and provides insights into user query statistics. Furthermore, Markprompt's UI components, available as React and Web Components, allow integration of prompts at all touch points of a company, from public websites websites to internal knowledge bases with just a few lines of code.
+
+Like Supabase, Markprompt is an open-source platform, allowing customers the freedom to host the dashboard and model backend on their own premises. Markprompt warmly welcomes contributions from the community, fostering collaboration and continuous improvement of the platform to meet the evolving needs of developers and technical users.
+
+
+ Building everything on Supabase from the Auth, to DB, to the vectors—this integrated experience
+ really made it for us.
+
+
+## The Results
+
+Choosing Supabase as their database and authentication solution empowered Markprompt to focus on developing innovative AI-powered solutions without compromising data security or impeding scalability. The seamless integration between Supabase and pgvector enabled them to meet the increasing demands of their expanding user base while upholding stringent GDPR requirements. Consequently, companies gain trust and confidence in the platform, solidifying its reputation as a robust enterprise offering.
+
+## Tech stack
+
+- Next.js - framework
+- Vercel - hosting
+- Typescript - language
+- Tailwind - CSS
+- Upstash - Redis and rate limiting
+- Supabase - database and auth
+- Stripe - payments
+- Plain - support chat
+- Fathom - analytics
diff --git a/apps/www/_customers/mendableai.mdx b/apps/www/_customers/mendableai.mdx
index 1a911c9f952..8b59a0c7246 100644
--- a/apps/www/_customers/mendableai.mdx
+++ b/apps/www/_customers/mendableai.mdx
@@ -1,7 +1,11 @@
---
-title: Mendable switches from Pinecone to Supabase for PostgreSQL vector embeddings.
name: Mendable
+title: Mendable switches from Pinecone to Supabase for PostgreSQL vector embeddings.
+# Use meta_title to add a custom meta title. Otherwise it defaults to '{name} | Supabase Customer Stories':
+# meta_title:
description: How Mendable boosts efficiency and accuracy of chat powered search for documentation using Supabase with pgvector.
+# Use meta_description to add a custom meta description. Otherwise it defaults to {description}:
+meta_description: How Mendable boosts efficiency and accuracy of chat powered search for documentation using Supabase with pgvector.
author: paul_copplestone
author_title: Supabase
author_url: https://github.com/kiwicopple
diff --git a/apps/www/_customers/mobbin.mdx b/apps/www/_customers/mobbin.mdx
index 06774869a3e..3149e98f8dd 100644
--- a/apps/www/_customers/mobbin.mdx
+++ b/apps/www/_customers/mobbin.mdx
@@ -1,7 +1,11 @@
---
-title: How Mobbin migrated 200,000 users from Firebase for a better authentication experience.
name: Mobbin
+title: How Mobbin migrated 200,000 users from Firebase for a better authentication experience.
+# Use meta_title to add a custom meta title. Otherwise it defaults to '{name} | Supabase Customer Stories':
+# meta_title:
description: Mobbin helps over 200,000 creators globally search and view the latest design patterns from well-known apps.
+# Use meta_description to add a custom meta description. Otherwise it defaults to {description}:
+meta_description: Mobbin helps over 200,000 creators globally search and view the latest design patterns from well-known apps.
author: rory_wilding
author_title: Supabase
author_url: https://github.com/kiwicopple
diff --git a/apps/www/_customers/replenysh.mdx b/apps/www/_customers/replenysh.mdx
index d6454cde443..32e05a4d2a3 100644
--- a/apps/www/_customers/replenysh.mdx
+++ b/apps/www/_customers/replenysh.mdx
@@ -1,7 +1,11 @@
---
-title: Replenysh uses Supabase to implement OTP in less than 24 hours.
name: Replenysh
+title: Replenysh uses Supabase to implement OTP in less than 24 hours.
+# Use meta_title to add a custom meta title. Otherwise it defaults to '{name} | Supabase Customer Stories':
+# meta_title:
description: With Supabase, Replenysh gets a slick auth experience, reduces DevOps overhead, and continues to scale with Postgres.
+# Use meta_description to add a custom meta description. Otherwise it defaults to {description}:
+meta_description: With Supabase, Replenysh gets a slick auth experience, reduces DevOps overhead, and continues to scale with Postgres.
author: rory_wilding
author_title: Supabase
author_url: https://github.com/kiwicopple
diff --git a/apps/www/_customers/xendit.mdx b/apps/www/_customers/xendit.mdx
index 7f3b8a131f7..6f05ecafc60 100644
--- a/apps/www/_customers/xendit.mdx
+++ b/apps/www/_customers/xendit.mdx
@@ -1,7 +1,11 @@
---
-title: Xendit use Supabase and create a full solution shipped to production in less than one week.
name: Xendit
+title: Xendit use Supabase and create a full solution shipped to production in less than one week.
+# Use meta_title to add a custom meta title. Otherwise it defaults to '{name} | Supabase Customer Stories':
+# meta_title:
description: As a payment processor, Xendit are responsible for verifying that all transactions are legal.
+# Use meta_description to add a custom meta description. Otherwise it defaults to {description}:
+meta_description: As a payment processor, Xendit are responsible for verifying that all transactions are legal.
author: rory_wilding
author_title: Supabase
author_url: https://github.com/kiwicopple
@@ -10,10 +14,10 @@ authorURL: https://github.com/kiwicopple
logo: /images/customers/logos/xendit.png
logo_inverse: /images/customers/logos/light/xendit.png
og_image: /images/customers/og/xendit.jpg
-company_url: https://xendit.co
tags:
- supabase
date: '2023-02-14'
+company_url: https://xendit.co
stats:
[
{ stat: '120', label: Staff count },
diff --git a/apps/www/components/BackedBy.tsx b/apps/www/components/BackedBy.tsx
new file mode 100644
index 00000000000..f796653e736
--- /dev/null
+++ b/apps/www/components/BackedBy.tsx
@@ -0,0 +1,52 @@
+import React from 'react'
+import { useRouter } from 'next/router'
+
+const BackedBy = ({
+ className,
+ layout = 'vertical',
+}: {
+ className?: string
+ layout?: 'horizontal' | 'vertical'
+}) => {
+ const { basePath } = useRouter()
+
+ return (
+
+ )
+}
+
+export default BackedBy
diff --git a/apps/www/components/BuiltWithSupabase/index.tsx b/apps/www/components/BuiltWithSupabase/index.tsx
index b467d87e570..de349708517 100644
--- a/apps/www/components/BuiltWithSupabase/index.tsx
+++ b/apps/www/components/BuiltWithSupabase/index.tsx
@@ -17,7 +17,7 @@ const BuiltExamples = () => {
-
diff --git a/apps/www/components/Carousels/ImageCarousel.tsx b/apps/www/components/Carousels/ImageCarousel.tsx
index a9b45a5597e..ef544266da7 100644
--- a/apps/www/components/Carousels/ImageCarousel.tsx
+++ b/apps/www/components/Carousels/ImageCarousel.tsx
@@ -125,6 +125,7 @@ function ImageCarousel(props: ImageCarouselProps) {
style={{ top: 0, left: 0 }}
frameBorder="0"
allow="autoplay; modestbranding; encrypted-media"
+ loading="lazy"
/>
)}
diff --git a/apps/www/components/CodeBlock/CodeBlock.tsx b/apps/www/components/CodeBlock/CodeBlock.tsx
index 7b40664cea9..732f558354e 100644
--- a/apps/www/components/CodeBlock/CodeBlock.tsx
+++ b/apps/www/components/CodeBlock/CodeBlock.tsx
@@ -126,6 +126,7 @@ function CodeBlock(props: CodeBlockProps) {
)
}
onClick={() => handleCopy()}
+ aria-label="Copy"
>
{/* {copied ? 'Copied' : 'Copy'} */}
diff --git a/apps/www/components/ExampleCard.tsx b/apps/www/components/ExampleCard.tsx
index 247eb9b8fa8..d0265ab6b8c 100644
--- a/apps/www/components/ExampleCard.tsx
+++ b/apps/www/components/ExampleCard.tsx
@@ -23,7 +23,7 @@ function ExampleCard(props: any) {
{props.author}
@@ -53,10 +53,14 @@ function ExampleCard(props: any) {
-
+
{props.vercel_deploy_url && (
-
+
)}
{props.demo_url && (
diff --git a/apps/www/components/Features/index.tsx b/apps/www/components/Features/index.tsx
index ab6510718e5..4af3829d6fe 100644
--- a/apps/www/components/Features/index.tsx
+++ b/apps/www/components/Features/index.tsx
@@ -49,7 +49,7 @@ const Features = () => {
const IconSections = Object.values(Solutions).map((solution: any) => {
const { name, description, icon, label, url } = solution
- if (solution.name === 'Realtime') return null
+
return (
@@ -77,8 +77,8 @@ const Features = () => {
return (
- Build faster and focus on your products
-
+ Build faster and focus on your products
+
{IconSections}
diff --git a/apps/www/components/Hero.tsx b/apps/www/components/Hero.tsx
deleted file mode 100644
index b8bec315b95..00000000000
--- a/apps/www/components/Hero.tsx
+++ /dev/null
@@ -1,137 +0,0 @@
-import { Button, IconBookOpen } from 'ui'
-import { useRouter } from 'next/router'
-import Link from 'next/link'
-import SectionContainer from './Layouts/SectionContainer'
-import Telemetry, { TelemetryEvent } from '~/lib/telemetry'
-import gaEvents from '~/lib/gaEvents'
-import { useTelemetryProps } from 'common/hooks/useTelemetryProps'
-
-const Hero = () => {
- const router = useRouter()
- const { basePath } = router
- const telemetryProps = useTelemetryProps()
-
- const sendTelemetryEvent = async (event: TelemetryEvent) => {
- await Telemetry.sendEvent(event, telemetryProps, router)
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
- Build in a weekend.
- Scale to millions.
-
-
-
- Supabase is an open source Firebase alternative. Start your project with a
- Postgres database, Authentication, instant APIs, Edge Functions, Realtime
- subscriptions, and Storage.
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/*
- Replace src with the following if have preference:
- Vimeo: https://player.vimeo.com/video/485959063?loop=1&autoplay=1
- Youtube: https://www.youtube-nocookie.com/embed/dBOSUER_5T4?playlist=dBOSUER_5T4&autoplay=1&loop=1&controls=0&modestbranding=1&rel=0&disablekb=1&mute=1&muted=1
- */}
-
-
-
-
-
-
-
-
- )
-}
-
-export default Hero
diff --git a/apps/www/components/Hero/Hero.tsx b/apps/www/components/Hero/Hero.tsx
new file mode 100644
index 00000000000..64dbdffe44a
--- /dev/null
+++ b/apps/www/components/Hero/Hero.tsx
@@ -0,0 +1,71 @@
+import Link from 'next/link'
+import { useRouter } from 'next/router'
+import Telemetry, { TelemetryEvent } from '~/lib/telemetry'
+import { useTelemetryProps } from 'common/hooks/useTelemetryProps'
+import gaEvents from '~/lib/gaEvents'
+import { Button, IconBookOpen } from 'ui'
+import SectionContainer from '~/components/Layouts/SectionContainer'
+import HeroFrameworks from './HeroFrameworks'
+import styles from './hero.module.css'
+
+const Hero = () => {
+ const router = useRouter()
+ const telemetryProps = useTelemetryProps()
+ const sendTelemetryEvent = async (event: TelemetryEvent) => {
+ await Telemetry.sendEvent(event, telemetryProps, router)
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ Build in a weekend
+
+
+ Scale to millions
+
+
+
+ Supabase is an open source Firebase alternative for building secure and
+ performant Postgres backends with minimal configuration.
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default Hero
diff --git a/apps/www/components/Hero/HeroFrameworks.tsx b/apps/www/components/Hero/HeroFrameworks.tsx
new file mode 100644
index 00000000000..3d59f73161b
--- /dev/null
+++ b/apps/www/components/Hero/HeroFrameworks.tsx
@@ -0,0 +1,107 @@
+import React from 'react'
+import Image from 'next/image'
+import Link from 'next/link'
+import ReactTooltip from 'react-tooltip'
+import Telemetry from '~/lib/telemetry'
+import { useTelemetryProps } from 'common/hooks/useTelemetryProps'
+import { useRouter } from 'next/router'
+
+const frameworks = [
+ {
+ name: 'Next.js',
+ icon: '/images/logos/frameworks/nextjs.svg',
+ docs: '/docs/guides/getting-started/quickstarts/nextjs',
+ gaEvent: 'www_hp_hero_frameworks_nextjs',
+ },
+ {
+ name: 'React',
+ icon: '/images/logos/frameworks/react.svg',
+ docs: '/docs/guides/getting-started/quickstarts/reactjs',
+ gaEvent: 'www_hp_hero_frameworks_react',
+ },
+ {
+ name: 'Nuxt',
+ icon: '/images/logos/frameworks/nuxtjs.svg',
+ docs: '/docs/guides/getting-started/quickstarts/nuxtjs',
+ gaEvent: 'www_hp_hero_frameworks_nuxt',
+ },
+ {
+ name: 'Flutter',
+ icon: '/images/logos/frameworks/flutter.svg',
+ docs: '/docs/guides/getting-started/quickstarts/flutter',
+ gaEvent: 'www_hp_hero_frameworks_flutter',
+ },
+ {
+ name: 'Svelte',
+ icon: '/images/logos/frameworks/svelte.svg',
+ docs: '/docs/guides/getting-started/quickstarts/sveltekit',
+ gaEvent: 'www_hp_hero_frameworks_svelte',
+ },
+ {
+ name: 'Python',
+ icon: '/images/logos/languages/python-icon.svg',
+ docs: '/docs/reference/python/introduction',
+ gaEvent: 'www_hp_hero_frameworks_python',
+ },
+ {
+ name: 'Vue',
+ icon: '/images/logos/frameworks/vue.svg',
+ docs: '/docs/guides/getting-started/quickstarts/vue',
+ gaEvent: 'www_hp_hero_frameworks_vue',
+ },
+]
+
+const HeroFrameworks = ({ className }: { className?: string }) => {
+ const router = useRouter()
+ const telemetryProps = useTelemetryProps()
+
+ const sendTelemetryEvent = async (gaEvent: string) => {
+ return await Telemetry.sendEvent(
+ {
+ action: gaEvent,
+ category: 'link',
+ label: '',
+ },
+ telemetryProps,
+ router
+ )
+ }
+
+ return (
+
+
Works seamlessly with 20+ frameworks
+
+
+ )
+}
+
+export default HeroFrameworks
diff --git a/apps/www/components/Hero/hero.module.css b/apps/www/components/Hero/hero.module.css
new file mode 100644
index 00000000000..f8e4fb0a77b
--- /dev/null
+++ b/apps/www/components/Hero/hero.module.css
@@ -0,0 +1,12 @@
+.hero-text {
+ opacity: 0;
+ transform: translate3d(0, 10px, 0);
+ animation: animateText 1s cubic-bezier(0.25, 0.25, 0, 1) 0.1s both;
+}
+
+@keyframes animateText {
+ to {
+ opacity: 1;
+ transform: translate3d(0, 0, 0);
+ }
+}
diff --git a/apps/www/components/InteractiveShimmerCard/index.tsx b/apps/www/components/InteractiveShimmerCard/index.tsx
new file mode 100644
index 00000000000..095dd02a429
--- /dev/null
+++ b/apps/www/components/InteractiveShimmerCard/index.tsx
@@ -0,0 +1,64 @@
+import React, { PropsWithChildren, useEffect, useRef } from 'react'
+
+const InteractiveShimmerCard = ({
+ outerClassName,
+ innerClassName,
+ children,
+}: PropsWithChildren<{ outerClassName?: string; innerClassName?: string }>) => {
+ const outerRef = useRef(null)
+ const innerRef = useRef(null)
+
+ const handleGlow = (event: any) => {
+ if (!outerRef.current || !innerRef.current) return null
+ const outerElement = outerRef.current as HTMLDivElement
+ const innerElement = innerRef.current as HTMLDivElement
+ let x: any
+ let y: any
+
+ const { x: elX, y: elY, width, height } = outerElement.getBoundingClientRect()
+ x = event.clientX - elX
+ y = event.clientY - elY
+ const isActive = x > -3 && x < width + 3 && y > -3 && y < height + 3
+ const activeGlow = isActive
+ ? `radial-gradient(65rem circle at ${x}px ${y}px, var(--colors-brand9), transparent), `
+ : ''
+ outerElement.style.background = isActive ? `var(--colors-brand9)` : `var(--colors-scale3)`
+ outerElement.style.backgroundImage = `
+ ${activeGlow}radial-gradient(30rem circle at ${x}px ${y}px, var(--colors-scale9), var(--colors-scale6))`
+
+ innerElement.style.backgroundImage = isActive
+ ? `radial-gradient(7rem circle at ${x}px ${y}px, var(--colors-scale5), transparent), radial-gradient(20rem circle at ${x}px ${y}px, var(--colors-scale4), transparent)`
+ : ''
+ }
+
+ useEffect(() => {
+ if (typeof window === 'undefined') return
+
+ window.addEventListener('mousemove', handleGlow)
+ return () => {
+ window.removeEventListener('mousemove', handleGlow)
+ }
+ }, [])
+
+ return (
+
+ )
+}
+
+export default InteractiveShimmerCard
diff --git a/apps/www/components/Nav/index.tsx b/apps/www/components/Nav/index.tsx
index 847d98db6bd..6f19e4b2be2 100644
--- a/apps/www/components/Nav/index.tsx
+++ b/apps/www/components/Nav/index.tsx
@@ -2,7 +2,7 @@ import React, { useState } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/router'
-import { Button, Badge, Announcement, AnnouncementCountdown, IconStar, IconChevronDown } from 'ui'
+import { Button, Badge, IconStar, IconChevronDown } from 'ui'
import FlyOut from '~/components/UI/FlyOut'
import Transition from 'lib/Transition'
@@ -197,9 +197,8 @@ const Nav = () => {
return (
<>
-
-
-
+ {/*
+ */}
{
/>
)
+ const meta = {
+ title: props.blog.meta_title ?? props.blog.title,
+ description: props.blog.meat_description ?? props.blog.description,
+ url: `https://supabase.com/blog/${props.blog.slug}`,
+ }
+
return (
<>
{
alt="team photo"
layout="fill"
objectFit="cover"
+ placeholder="blur"
+ blurDataURL="/images/blur.png"
className="rounded-md"
/>
-
+
{
alt="team photo"
layout="fill"
objectFit="cover"
+ placeholder="blur"
+ blurDataURL="/images/blur.png"
className="rounded-md"
/>
@@ -222,6 +228,8 @@ const CareerPage: NextPage = ({ jobs, contributors }: any) => {
alt="team photo"
layout="fill"
objectFit="cover"
+ placeholder="blur"
+ blurDataURL="/images/blur.png"
className="rounded-md"
/>
@@ -233,18 +241,22 @@ const CareerPage: NextPage = ({ jobs, contributors }: any) => {
alt="team photo"
layout="fill"
objectFit="cover"
+ placeholder="blur"
+ blurDataURL="/images/blur.png"
className="rounded-md"
/>
-
-
+
+
diff --git a/apps/www/pages/customers/[slug].tsx b/apps/www/pages/customers/[slug].tsx
index 3e5cb7aaa62..3a37a26ba6f 100644
--- a/apps/www/pages/customers/[slug].tsx
+++ b/apps/www/pages/customers/[slug].tsx
@@ -62,8 +62,8 @@ function CaseStudyPage(props: any) {
const { basePath } = useRouter()
const meta = {
- title: `${props.blog.name} | Supabase Customer Stories`,
- description: props.blog.description,
+ title: props.blog.meta_title ?? `${props.blog.name} | Supabase Customer Stories`,
+ description: props.blog.meta_description ?? props.blog.description,
image: props.blog.og_image ?? `${basePath}/images/customers/og/customer-stories.jpg`,
url: `https://supabase.io/customers/${props.blog.slug}`,
}
diff --git a/apps/www/pages/index.tsx b/apps/www/pages/index.tsx
index 91fdb6095af..37a1365aa72 100644
--- a/apps/www/pages/index.tsx
+++ b/apps/www/pages/index.tsx
@@ -1,38 +1,36 @@
-import { getSortedPosts, getAllCategories } from '~/lib/posts'
+import dynamic from 'next/dynamic'
+import { getSortedPosts } from '~/lib/posts'
import PostTypes from '~/types/post'
-import Container from 'components/Container'
import Layout from '~/components/Layouts/Default'
-import Hero from 'components/Hero'
-
-import Features from 'components/Features/index'
-import BuiltExamples from 'components/BuiltWithSupabase/index'
-import MadeForDevelopers from 'components/MadeForDevelopers/index'
-import AdminAccess from 'components/AdminAccess/index'
-import CTABanner from 'components/CTABanner/index'
-import CustomerStories from 'components/CustomerStories'
-import TwitterSocialSection from '~/components/TwitterSocialSection'
+import Hero from '~/components/Hero/Hero'
// Import Swiper styles if swiper used on page
import 'swiper/swiper.min.css'
+const Features = dynamic(() => import('~/components/Features/index'))
+const BackedBy = dynamic(() => import('~/components/BackedBy'))
+const BuiltExamples = dynamic(() => import('components/BuiltWithSupabase/index'))
+const MadeForDevelopers = dynamic(() => import('components/MadeForDevelopers/index'))
+const AdminAccess = dynamic(() => import('components/AdminAccess/index'))
+const CTABanner = dynamic(() => import('components/CTABanner/index'))
+const CustomerStories = dynamic(() => import('components/CustomerStories'))
+const TwitterSocialSection = dynamic(() => import('~/components/TwitterSocialSection'))
+
type Props = { customerStories: PostTypes[] }
const Index = ({ customerStories }: Props) => {
return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
- >
+
+
+
+
+
+
+
+
+
+
+
)
}
diff --git a/apps/www/pages/privacy.mdx b/apps/www/pages/privacy.mdx
index 96c869ca552..56c5018e669 100644
--- a/apps/www/pages/privacy.mdx
+++ b/apps/www/pages/privacy.mdx
@@ -2,7 +2,7 @@ import Layout from '../layouts/Layout'
import SectionContainer from '../components/Layouts/SectionContainer'
export const meta = {
- title: 'Privacy Policy',
+ title: 'Privacy Policy | Supabase',
description: 'Supabase Privacy Policy',
}
diff --git a/apps/www/pages/sla.mdx b/apps/www/pages/sla.mdx
index e535758d638..228f9316e71 100644
--- a/apps/www/pages/sla.mdx
+++ b/apps/www/pages/sla.mdx
@@ -2,7 +2,7 @@ import Layout from '../layouts/Layout'
import SectionContainer from '../components/Layouts/SectionContainer'
export const meta = {
- title: 'Service Level Agreement',
+ title: 'Service Level Agreement | Supabase',
description: 'Supabase Service Level Agreement',
}
diff --git a/apps/www/pages/supasquad.mdx b/apps/www/pages/supasquad.mdx
index e305eab4b73..04eee13ef51 100644
--- a/apps/www/pages/supasquad.mdx
+++ b/apps/www/pages/supasquad.mdx
@@ -2,7 +2,7 @@ import Layout from '../layouts/Layout'
import SectionContainer from '../components/Layouts/SectionContainer'
export const meta = {
- title: 'SupaSquad',
+ title: 'SupaSquad | Supabase',
description: 'Supabase Advocate program',
}
diff --git a/apps/www/pages/support-policy.mdx b/apps/www/pages/support-policy.mdx
new file mode 100644
index 00000000000..51bf170c482
--- /dev/null
+++ b/apps/www/pages/support-policy.mdx
@@ -0,0 +1,83 @@
+import Layout from '../layouts/Layout'
+import SectionContainer from '../components/Layouts/SectionContainer'
+
+export const meta = {
+ title: 'Support Policy',
+ description: 'Supabase Support Policy',
+}
+
+
+
+# Support Policy
+
+Supabase is dedicated to providing an exceptional customer experience. As a crucial aspect of
+this commitment, we offer limited technical support for all paid projects running on our hosted Supabase platform. Technical support is confined to the scope, hours, contacts, and channels below.
+
+## Scope
+
+Supabase's support offering is available only for the technologies supported by the Supabase Platform and is restricted to the following:
+
+**Configuration Issues**
+
+- Installation of extensions
+- Best practices for auth configuration
+- General questions about the supabase stack
+
+**Troubleshooting**
+
+- Providing workarounds or resolutions for known problems
+- Answering general how-to questions and providing pointers to documentation
+- Troubleshooting supported software packages (Realtime, Edge Functions, Webhooks, PostgREST, Auth) exhibiting erratic or faulty behavior on Supabase, independent of the user's application code
+
+**Not Covered**
+
+Supabase technical support services do not extend to the following areas:
+
+- General debugging of user applications
+- Rewriting application code for compatibility with Supabase
+- Modifying and/or patching third-party or Open Source software packages for compatibility with Supabase
+
+## Who can contact support?
+
+Billing and account management support is available to all customers.
+
+Limited technical support is accessible for Supabase customers utilizing paid Supabase resources and/or paid Supabase Add-Ons. Support requests will only be processed if:
+
+- The request is made through one of our official support channels (see below)
+- The request originates from a registered Supabase account email address
+- The requester has Developer or higher access to any specific Supabase projects pertaining to the request.
+
+## Official Support Channels
+
+- Web and Dashboard: Support is available exclusively through the Supabase help feature in the dashboard or this website -[https://app.supabase.com/support/new](https://app.supabase.com/support/new)
+- Email: If for some reason you are unable to access the dashboard or ticket from you can contact support by emailing support@supabase.com from a registered Supabase account email address
+
+## Community Support
+
+Supabase support staff will only address support requests received through the official channels mentioned earlier. However, community channels may exist for peer-to-peer support and discussions. This is support provided by volunteers that contribute to the Supabase community.
+
+For questions related to debugging code, we recommend reaching out on **[GitHub Discussions](https://github.com/supabase/supabase/discussions)** or on **[Discord](https://discord.supabase.com/)**. The community is made up of experienced developers who may be able to provide guidance and support with code-related issues.
+
+To get the most helpful response from the community, providing precise and detailed information about your problem and any error messages you may be encountering is important. Be sure to **include any relevant code snippets explaining how to reproduce** the issue in your message.
+
+Please note that Supabase team members may participate in community channels at their discretion, but there is no guarantee of response for support issues unless they are
+submitted through one of the official channels above.
+
+## Service Level Agreement
+
+Service Level Agreements are only available to Enterprise customers. You can details of our SLA including support response times on the dedicated page for this topic -
+[https://supabase.com/sla](https://supabase.com/sla)
+
+## Proactive Monitoring
+
+In the event of a platform issue, a notice will be posted on our platform status site at [status.supabase.com](https://status.supabase.com/) to promptly communicate the impact and status of any such issue. You do not need to submit a support ticket if there's an ongoing platform issue. Instead, monitor the status page which will always be kept up-to-date with the latest progress and information.
+
+## Premium Support
+
+Please contact us to find out about our Premium Support options available for Teams Tier and Enterprise customers using the form below:
+
+[https://forms.supabase.com/enterprise](https://forms.supabase.com/enterprise)
+
+
+
+export default (context) =>
diff --git a/apps/www/public/customers-rss.xml b/apps/www/public/customers-rss.xml
index 6835a30b98e..488aab0590b 100644
--- a/apps/www/public/customers-rss.xml
+++ b/apps/www/public/customers-rss.xml
@@ -9,9 +9,9 @@
-
https://supabase.com/customers/mendableai
- Mendable.ai switches from Pinecone to Supabase for PostgreSQL vector embeddings.
+ Mendable switches from Pinecone to Supabase for PostgreSQL vector embeddings.
https://supabase.com/customers/mendableai
- How Mendable.ai boosts efficiency and accuracy of chat powered search for documentation using Supabase with pg_vector.
+ How Mendable boosts efficiency and accuracy of chat powered search for documentation using Supabase with pgvector.
Thu, 04 May 2023 22:10:00 +0000
-
diff --git a/apps/www/public/images/blog/2023-05-15-chatgpt-plugin-temnplate-deno/chatgpt_plugin_demo_localhost.png b/apps/www/public/images/blog/2023-05-15-chatgpt-plugin-temnplate-deno/chatgpt_plugin_demo_localhost.png
new file mode 100644
index 00000000000..f8ef43d078a
Binary files /dev/null and b/apps/www/public/images/blog/2023-05-15-chatgpt-plugin-temnplate-deno/chatgpt_plugin_demo_localhost.png differ
diff --git a/apps/www/public/images/blog/2023-05-15-chatgpt-plugin-temnplate-deno/chatgpt_plugin_template_deno.jpeg b/apps/www/public/images/blog/2023-05-15-chatgpt-plugin-temnplate-deno/chatgpt_plugin_template_deno.jpeg
new file mode 100644
index 00000000000..f63ae65dd7c
Binary files /dev/null and b/apps/www/public/images/blog/2023-05-15-chatgpt-plugin-temnplate-deno/chatgpt_plugin_template_deno.jpeg differ
diff --git a/apps/www/public/images/blog/avatars/marie-schneegans.jpeg b/apps/www/public/images/blog/avatars/marie-schneegans.jpeg
new file mode 100644
index 00000000000..2489f5ae7a5
Binary files /dev/null and b/apps/www/public/images/blog/avatars/marie-schneegans.jpeg differ
diff --git a/apps/www/public/images/blog/avatars/michael-fester.jpeg b/apps/www/public/images/blog/avatars/michael-fester.jpeg
new file mode 100644
index 00000000000..64fb08584d3
Binary files /dev/null and b/apps/www/public/images/blog/avatars/michael-fester.jpeg differ
diff --git a/apps/www/public/images/blog/flutter-hackathon/discord.png b/apps/www/public/images/blog/flutter-hackathon/discord.png
new file mode 100644
index 00000000000..3d2f0910c1e
Binary files /dev/null and b/apps/www/public/images/blog/flutter-hackathon/discord.png differ
diff --git a/apps/www/public/images/blog/flutter-hackathon/keyboard.png b/apps/www/public/images/blog/flutter-hackathon/keyboard.png
new file mode 100644
index 00000000000..9f7768952f7
Binary files /dev/null and b/apps/www/public/images/blog/flutter-hackathon/keyboard.png differ
diff --git a/apps/www/public/images/blog/flutter-hackathon/stickers.png b/apps/www/public/images/blog/flutter-hackathon/stickers.png
new file mode 100644
index 00000000000..44c0e3c82ff
Binary files /dev/null and b/apps/www/public/images/blog/flutter-hackathon/stickers.png differ
diff --git a/apps/www/public/images/blog/flutter-hackathon/thumbnail.jpg b/apps/www/public/images/blog/flutter-hackathon/thumbnail.jpg
new file mode 100644
index 00000000000..6db1f961d21
Binary files /dev/null and b/apps/www/public/images/blog/flutter-hackathon/thumbnail.jpg differ
diff --git a/apps/www/public/images/career/1.jpg b/apps/www/public/images/career/1.jpg
index cc2feb0edf7..3b3fa6eb6e0 100644
Binary files a/apps/www/public/images/career/1.jpg and b/apps/www/public/images/career/1.jpg differ
diff --git a/apps/www/public/images/career/2.jpg b/apps/www/public/images/career/2.jpg
index 81e2f6db6fa..8063c2bbc9e 100644
Binary files a/apps/www/public/images/career/2.jpg and b/apps/www/public/images/career/2.jpg differ
diff --git a/apps/www/public/images/career/3.jpg b/apps/www/public/images/career/3.jpg
index 2eec177a69b..86232fed188 100644
Binary files a/apps/www/public/images/career/3.jpg and b/apps/www/public/images/career/3.jpg differ
diff --git a/apps/www/public/images/career/4.jpg b/apps/www/public/images/career/4.jpg
index 3338f52814d..1e50d31dc12 100644
Binary files a/apps/www/public/images/career/4.jpg and b/apps/www/public/images/career/4.jpg differ
diff --git a/apps/www/public/images/career/5.jpg b/apps/www/public/images/career/5.jpg
index 241b22cb010..923385e7613 100644
Binary files a/apps/www/public/images/career/5.jpg and b/apps/www/public/images/career/5.jpg differ
diff --git a/apps/www/public/images/career/6.jpg b/apps/www/public/images/career/6.jpg
index 77093bcd102..f0a98ef2f23 100644
Binary files a/apps/www/public/images/career/6.jpg and b/apps/www/public/images/career/6.jpg differ
diff --git a/apps/www/public/images/customers/logos/light/markprompt.png b/apps/www/public/images/customers/logos/light/markprompt.png
new file mode 100644
index 00000000000..f36674a3299
Binary files /dev/null and b/apps/www/public/images/customers/logos/light/markprompt.png differ
diff --git a/apps/www/public/images/customers/logos/markprompt.png b/apps/www/public/images/customers/logos/markprompt.png
new file mode 100644
index 00000000000..3a6eea8c2a0
Binary files /dev/null and b/apps/www/public/images/customers/logos/markprompt.png differ
diff --git a/apps/www/public/images/customers/og/markprompt.png b/apps/www/public/images/customers/og/markprompt.png
new file mode 100644
index 00000000000..a0491f6e480
Binary files /dev/null and b/apps/www/public/images/customers/og/markprompt.png differ
diff --git a/apps/www/public/images/logos/frameworks/dart.svg b/apps/www/public/images/logos/frameworks/dart.svg
new file mode 100644
index 00000000000..7def1de37ae
--- /dev/null
+++ b/apps/www/public/images/logos/frameworks/dart.svg
@@ -0,0 +1,3 @@
+
diff --git a/apps/www/public/images/logos/frameworks/expo.svg b/apps/www/public/images/logos/frameworks/expo.svg
new file mode 100644
index 00000000000..431873ad343
--- /dev/null
+++ b/apps/www/public/images/logos/frameworks/expo.svg
@@ -0,0 +1,3 @@
+
diff --git a/apps/www/public/images/logos/frameworks/flutter.svg b/apps/www/public/images/logos/frameworks/flutter.svg
new file mode 100644
index 00000000000..e3f2bb362b8
--- /dev/null
+++ b/apps/www/public/images/logos/frameworks/flutter.svg
@@ -0,0 +1,4 @@
+
diff --git a/apps/www/public/images/logos/frameworks/flutterflow.svg b/apps/www/public/images/logos/frameworks/flutterflow.svg
new file mode 100644
index 00000000000..faa236f9da7
--- /dev/null
+++ b/apps/www/public/images/logos/frameworks/flutterflow.svg
@@ -0,0 +1,3 @@
+
diff --git a/apps/www/public/images/logos/frameworks/nextjs.svg b/apps/www/public/images/logos/frameworks/nextjs.svg
new file mode 100644
index 00000000000..4c59f3e2356
--- /dev/null
+++ b/apps/www/public/images/logos/frameworks/nextjs.svg
@@ -0,0 +1,3 @@
+
diff --git a/apps/www/public/images/logos/frameworks/nuxtjs.svg b/apps/www/public/images/logos/frameworks/nuxtjs.svg
new file mode 100644
index 00000000000..36c92c138a4
--- /dev/null
+++ b/apps/www/public/images/logos/frameworks/nuxtjs.svg
@@ -0,0 +1,3 @@
+
diff --git a/apps/www/public/images/logos/frameworks/react.svg b/apps/www/public/images/logos/frameworks/react.svg
new file mode 100644
index 00000000000..c5f5baf60f7
--- /dev/null
+++ b/apps/www/public/images/logos/frameworks/react.svg
@@ -0,0 +1,3 @@
+
diff --git a/apps/www/public/images/logos/frameworks/reactiveX.svg b/apps/www/public/images/logos/frameworks/reactiveX.svg
new file mode 100644
index 00000000000..aad5a9b9599
--- /dev/null
+++ b/apps/www/public/images/logos/frameworks/reactiveX.svg
@@ -0,0 +1,3 @@
+
diff --git a/apps/www/public/images/logos/frameworks/redwood.svg b/apps/www/public/images/logos/frameworks/redwood.svg
new file mode 100644
index 00000000000..16d20380072
--- /dev/null
+++ b/apps/www/public/images/logos/frameworks/redwood.svg
@@ -0,0 +1,3 @@
+
diff --git a/apps/www/public/images/logos/frameworks/remix.svg b/apps/www/public/images/logos/frameworks/remix.svg
new file mode 100644
index 00000000000..70574417986
--- /dev/null
+++ b/apps/www/public/images/logos/frameworks/remix.svg
@@ -0,0 +1,4 @@
+
diff --git a/apps/www/public/images/logos/frameworks/solidjs.svg b/apps/www/public/images/logos/frameworks/solidjs.svg
new file mode 100644
index 00000000000..5bcd9f30b33
--- /dev/null
+++ b/apps/www/public/images/logos/frameworks/solidjs.svg
@@ -0,0 +1,7 @@
+
diff --git a/apps/www/public/images/logos/frameworks/svelte.svg b/apps/www/public/images/logos/frameworks/svelte.svg
new file mode 100644
index 00000000000..8aaafe1488f
--- /dev/null
+++ b/apps/www/public/images/logos/frameworks/svelte.svg
@@ -0,0 +1,3 @@
+
diff --git a/apps/www/public/images/logos/frameworks/turbopack.svg b/apps/www/public/images/logos/frameworks/turbopack.svg
new file mode 100644
index 00000000000..3fd8d59c367
--- /dev/null
+++ b/apps/www/public/images/logos/frameworks/turbopack.svg
@@ -0,0 +1,7 @@
+
diff --git a/apps/www/public/images/logos/frameworks/vue.svg b/apps/www/public/images/logos/frameworks/vue.svg
new file mode 100644
index 00000000000..587ccb9e762
--- /dev/null
+++ b/apps/www/public/images/logos/frameworks/vue.svg
@@ -0,0 +1,3 @@
+
diff --git a/apps/www/public/images/logos/languages/python-icon.svg b/apps/www/public/images/logos/languages/python-icon.svg
new file mode 100644
index 00000000000..3f5555419cf
--- /dev/null
+++ b/apps/www/public/images/logos/languages/python-icon.svg
@@ -0,0 +1,3 @@
+
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-gregnr-rss.xml b/apps/www/public/planetpg-gregnr-rss.xml
index c537ef6bf5d..e9c9b9954a8 100644
--- a/apps/www/public/planetpg-gregnr-rss.xml
+++ b/apps/www/public/planetpg-gregnr-rss.xml
@@ -5,14 +5,14 @@
https://supabase.com/blog
Latest Postgres news from Greg Richardson at Supabase
en
- Sun, 05 Feb 2023 03:03:30 +0000
+ Sun, 05 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
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 0e3baf3fb66..a3cb7fe2fd5 100644
--- a/apps/www/public/planetpg-oli_rice-rss.xml
+++ b/apps/www/public/planetpg-oli_rice-rss.xml
@@ -5,49 +5,49 @@
https://supabase.com/blog
Latest Postgres news from Oliver Rice at Supabase
en
- Thu, 20 Apr 2023 02:02:30 +0000
+ Thu, 20 Apr 2023 22:10:00 +0000
-
https://supabase.com/blog/whats-new-in-pg-graphql-v1-2
What's New in pg_graphql v1.2
https://supabase.com/blog/whats-new-in-pg-graphql-v1-2
New Features in the v1.2 release of pg_graphql
- Thu, 20 Apr 2023 02:02:30 +0000
+ Thu, 20 Apr 2023 22:10: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
+ https://supabase.com/blog/postgres-audit
Postgres Auditing in 150 lines of SQL
- https://supabase.com/blog/audit
+ https://supabase.com/blog/postgres-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 b67aabf2433..7e44f582f2b 100644
--- a/apps/www/public/planetpg-paul_copplestone-rss.xml
+++ b/apps/www/public/planetpg-paul_copplestone-rss.xml
@@ -5,49 +5,49 @@
https://supabase.com/blog
Latest Postgres news from Paul Copplestone at Supabase
en
- Sun, 30 Apr 2023 02:02:30 +0000
+ Sun, 30 Apr 2023 22:10:00 +0000
-
https://supabase.com/blog/postgres-pluggable-strorage
Next steps for Postgres pluggable storage
https://supabase.com/blog/postgres-pluggable-strorage
Exploring history of Postgres pluggable storage and the possibility of landing it in the Postgres core.
- Sun, 30 Apr 2023 02:02:30 +0000
+ Sun, 30 Apr 2023 22:10: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 9ea9162ea31..6de60b1e5b8 100644
--- a/apps/www/public/rss.xml
+++ b/apps/www/public/rss.xml
@@ -5,1246 +5,1274 @@
https://supabase.com
Latest news from Supabase
en
- Sun, 30 Apr 2023 02:02:30 +0000
+ Sun, 14 May 2023 22:10:00 +0000
-
+ https://supabase.com/blog/building-chatgpt-plugins-template
+ Building ChatGPT Plugins with Supabase Edge Runtime
+ https://supabase.com/blog/building-chatgpt-plugins-template
+ We're releasing a ChatGPT plugin template written in TypeScript and running on Deno!
+ Sun, 14 May 2023 22:10:00 +0000
+
+-
+ https://supabase.com/blog/flutter-hackathon
+ Flutter Hackathon
+ https://supabase.com/blog/flutter-hackathon
+ Build Flutter apps and win limited edition swag.
+ Thu, 11 May 2023 22:10:00 +0000
+
+-
+ https://supabase.com/blog/supabase-beta-update-april-2023
+ Supabase Beta April 2023
+ https://supabase.com/blog/supabase-beta-update-april-2023
+ A review of Launch Week 7 and more exciting updates from last month.
+ Mon, 08 May 2023 22:10:00 +0000
+
+-
+ https://supabase.com/blog/flutter-multi-factor-authentication
+ Securing your Flutter apps with Multi-Factor Authentication
+ https://supabase.com/blog/flutter-multi-factor-authentication
+ Build a Flutter app where the user is required to authenticate using Multi-Factor Authentication.
+ Wed, 03 May 2023 22:10:00 +0000
+
+-
https://supabase.com/blog/postgres-pluggable-strorage
Next steps for Postgres pluggable storage
https://supabase.com/blog/postgres-pluggable-strorage
Exploring history of Postgres pluggable storage and the possibility of landing it in the Postgres core.
- Sun, 30 Apr 2023 02:02:30 +0000
+ Sun, 30 Apr 2023 22:10:00 +0000
-
https://supabase.com/blog/launch-week-7-hackathon-winners
Launch Week 7 Hackathon Winners
https://supabase.com/blog/launch-week-7-hackathon-winners
Announcing the winners of the Launch Week 7 Hackathon!
- Sun, 23 Apr 2023 02:02:30 +0000
+ Sun, 23 Apr 2023 22:10:00 +0000
-
https://supabase.com/blog/whats-new-in-pg-graphql-v1-2
What's New in pg_graphql v1.2
https://supabase.com/blog/whats-new-in-pg-graphql-v1-2
New Features in the v1.2 release of pg_graphql
- Thu, 20 Apr 2023 02:02:30 +0000
+ Thu, 20 Apr 2023 22:10:00 +0000
-
https://supabase.com/blog/supabase-studio-2.0
Supabase Studio 2.0: help when you need it most
https://supabase.com/blog/supabase-studio-2.0
- undefined
- Thu, 13 Apr 2023 02:02:30 +0000
+ Supabase Studio now comes with ChatGPT, and GraphiQL built in, Cascade Deletes, and Foreign Key Selectors, and much more.
+ Thu, 13 Apr 2023 22:10:00 +0000
-
https://supabase.com/blog/dbdev
dbdev: PostgreSQL Package Manager
https://supabase.com/blog/dbdev
We're publicly previewing dbdev, a PostgreSQL package manager.
- Thu, 13 Apr 2023 02:02:30 +0000
+ Thu, 13 Apr 2023 22:10:00 +0000
-
https://supabase.com/blog/pg-tle
Trusted Language Extensions for Postgres
https://supabase.com/blog/pg-tle
- undefined
- Thu, 13 Apr 2023 02:02:30 +0000
+ We're collaborating with AWS to bring Trusted Language Extensions to Postgres.
+ Thu, 13 Apr 2023 22:10:00 +0000
-
https://supabase.com/blog/launch-week-7-community-highlights
Launch Week 7 Community Highlights
https://supabase.com/blog/launch-week-7-community-highlights
- undefined
- Thu, 13 Apr 2023 02:02:30 +0000
+ We're honored to work with, sponsor, and support incredible people and tools. Here is a highlight of the last 3 months.
+ Thu, 13 Apr 2023 22:10:00 +0000
-
https://supabase.com/blog/supabase-auth-sso-pkce
Supabase Auth: SSO, Mobile, and Server-side support
https://supabase.com/blog/supabase-auth-sso-pkce
- undefined
- Wed, 12 Apr 2023 02:02:30 +0000
+ Supacharging Supabase Auth with Sign in with Apple on iOS, Single-Sign-On support with SAML 2.0, and PKCE for server-side rendering and mobile auth.
+ Wed, 12 Apr 2023 22:10:00 +0000
-
https://supabase.com/blog/storage-v3-resumable-uploads
Supabase Storage v3: Resumable Uploads with support for 50GB files
https://supabase.com/blog/storage-v3-resumable-uploads
- undefined
- Tue, 11 Apr 2023 02:02:30 +0000
+ Storage V3 with lots of new features including resumable uploads, more image transformationsm a Next.js image loader and more.
+ Tue, 11 Apr 2023 22:10:00 +0000
-
https://supabase.com/blog/edge-runtime-self-hosted-deno-functions
Supabase Edge Runtime: Self-hosted Deno Functions
https://supabase.com/blog/edge-runtime-self-hosted-deno-functions
- undefined
- Mon, 10 Apr 2023 02:02:30 +0000
+ We are open-sourcing Supabase Edge Runtime allowing you to host your Edge Functions anywhere.
+ Mon, 10 Apr 2023 22:10:00 +0000
-
https://supabase.com/blog/supabase-logs-self-hosted
Supabase Logs: open source logging server
https://supabase.com/blog/supabase-logs-self-hosted
- undefined
- Sun, 09 Apr 2023 02:02:30 +0000
+ We're releasing Supabase Logs for both self-hosted users and CLI development.
+ Sun, 09 Apr 2023 22:10:00 +0000
-
https://supabase.com/blog/supabase-beta-update-march-2023
Supabase Beta March 2023
https://supabase.com/blog/supabase-beta-update-march-2023
We are in full shipping mode 🛥️… Launch Week 7 can’t come quickly enough!
- Fri, 07 Apr 2023 02:02:30 +0000
+ Fri, 07 Apr 2023 22:10:00 +0000
-
https://supabase.com/blog/launch-week-7-hackathon
The Supabase AI Hackathon
https://supabase.com/blog/launch-week-7-hackathon
Build an Open Source Project over 10 days. 5 prize categories.
- Thu, 06 Apr 2023 02:02:30 +0000
+ Thu, 06 Apr 2023 22:10:00 +0000
-
https://supabase.com/blog/designing-with-ai-midjourney
Designing with AI: Generating unique artwork for every user
https://supabase.com/blog/designing-with-ai-midjourney
Using MidJourney to generative artwork and serving ticket images with Edge Functions
- Thu, 06 Apr 2023 02:02:30 +0000
+ Thu, 06 Apr 2023 22:10:00 +0000
-
https://supabase.com/blog/infinite-scroll-with-nextjs-framer-motion
Infinite scroll with Next.js, Framer Motion, and Supabase
https://supabase.com/blog/infinite-scroll-with-nextjs-framer-motion
Lazy load and paginate data on scroll with Next.js and a sprinkle of Framer Motion magic ✨
- Mon, 03 Apr 2023 02:02:30 +0000
+ Mon, 03 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.
- 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
+ https://supabase.com/blog/postgres-audit
Postgres Auditing in 150 lines of SQL
- https://supabase.com/blog/audit
+ https://supabase.com/blog/postgres-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/apps/www/styles/index.css b/apps/www/styles/index.css
index fe2809a4a07..340d3162840 100644
--- a/apps/www/styles/index.css
+++ b/apps/www/styles/index.css
@@ -10,6 +10,7 @@
url(../../../packages/common/assets/fonts/CustomFont-Book.woff) format('woff');
font-weight: 400;
font-style: normal;
+ font-display: swap;
}
@font-face {
@@ -18,6 +19,7 @@
url(../../../packages/common/assets/fonts/CustomFont-Medium.woff) format('woff');
font-weight: 500;
font-style: normal;
+ font-display: swap;
}
html {
diff --git a/examples/auth/nextjs/middleware.ts b/examples/auth/nextjs/middleware.ts
index 24b65f84742..e775a9d9931 100644
--- a/examples/auth/nextjs/middleware.ts
+++ b/examples/auth/nextjs/middleware.ts
@@ -7,6 +7,15 @@ import type { Database } from "./lib/database.types";
export async function middleware(req: NextRequest) {
const res = NextResponse.next();
const supabase = createMiddlewareSupabaseClient({ req, res });
- await supabase.auth.getSession();
+
+ const { data: { session } } = await supabase.auth.getSession(); // destructure the data object to obtain the session object
+
+ if (session === null) return NextResponse.redirect(new URL("/login", req.nextUrl));
+ // instead of "/login" you can redirect to any other route in case the user is not logged in
+
return res;
}
+
+export const config = {
+ matcher: [], // add the routes you wish the middleware to run in. You can also use regex
+};
diff --git a/examples/edge-functions/supabase/functions/upstash-redis-ratelimit/README.md b/examples/edge-functions/supabase/functions/upstash-redis-ratelimit/README.md
new file mode 100644
index 00000000000..86fe57b6387
--- /dev/null
+++ b/examples/edge-functions/supabase/functions/upstash-redis-ratelimit/README.md
@@ -0,0 +1,33 @@
+# Rate limiting with Upstash Redis in Supabase Edge Functions
+
+[Redis](https://redis.io/docs/about/) is an open source (BSD licensed), in-memory data structure store used as a database, cache, message broker, and streaming engine. It is optimized for atomic operations like incrementing a value, for example for a view counter or rate limiting. We can even rate limit based on the user ID from Supabase Auth!
+
+[Upstash](https://upstash.com/) provides an HTTP/REST based Redis client which is ideal for serverless use-cases and therefore works well with Supabase Edge Functions.
+
+## Redis database setup
+
+Create a Redis database using the [Upstash Console](https://console.upstash.com/) or [Upstash CLI](https://github.com/upstash/cli).
+
+Select the `Global` type to minimize the latency from all edge locations. Copy the `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` to your .env file. You'll find them under **Details > REST API > .env**.
+
+```bash
+cp supabase/.env.local.example supabase/.env.local
+```
+
+## Run locally
+
+Make sure you have the latest version of the [Supabase CLI installed](https://supabase.com/docs/guides/cli#installation).
+
+```bash
+supabase start
+supabase functions serve --env-file supabase/.env.local
+```
+
+Navigate to http://localhost:54321/functions/v1/upstash-redis-ratelimit.
+
+## Deploy
+
+```bash
+supabase functions deploy upstash-redis-ratelimit
+supabase secrets set --env-file supabase/.env.local
+```
diff --git a/examples/edge-functions/supabase/functions/upstash-redis-ratelimit/index.ts b/examples/edge-functions/supabase/functions/upstash-redis-ratelimit/index.ts
new file mode 100644
index 00000000000..0f934d1864a
--- /dev/null
+++ b/examples/edge-functions/supabase/functions/upstash-redis-ratelimit/index.ts
@@ -0,0 +1,63 @@
+import { serve } from "std/server";
+import { Redis } from "@upstash/redis";
+import { Ratelimit } from "@upstash/ratelimit";
+import { createClient } from "@supabase/supabase-js";
+
+console.log(`Function "upstash-redis-counter" up and running!`);
+
+serve(async (req) => {
+ try {
+ // Create a Supabase client with the Auth context of the logged in user.
+ const supabaseClient = createClient(
+ // Supabase API URL - env var exported by default.
+ Deno.env.get("SUPABASE_URL") ?? "",
+ // Supabase API ANON KEY - env var exported by default.
+ Deno.env.get("SUPABASE_ANON_KEY") ?? "",
+ // Create client with Auth context of the user that called the function.
+ // This way your row-level-security (RLS) policies are applied.
+ {
+ global: {
+ headers: { Authorization: req.headers.get("Authorization")! },
+ },
+ }
+ );
+ // Now we can get the session or user object
+ const {
+ data: { user },
+ } = await supabaseClient.auth.getUser();
+ if (!user) throw new Error("no user");
+ console.log(user.id);
+
+ const redis = new Redis({
+ url: Deno.env.get("UPSTASH_REDIS_REST_URL")!,
+ token: Deno.env.get("UPSTASH_REDIS_REST_TOKEN")!,
+ });
+
+ // Create a new ratelimiter, that allows 10 requests per 10 seconds
+ const ratelimit = new Ratelimit({
+ redis,
+ limiter: Ratelimit.slidingWindow(2, "10 s"),
+ analytics: true,
+ });
+
+ // Use a constant string to limit all requests with a single ratelimit
+ // Or use a userID, apiKey or ip address for individual limits.
+ const identifier = user.id;
+ const { success } = await ratelimit.limit(identifier);
+
+ if (!success) {
+ throw new Error("limit exceeded");
+ }
+
+ return new Response(JSON.stringify({ success }), { status: 200 });
+ } catch (error) {
+ return new Response(JSON.stringify({ error: error.message }), {
+ status: 200,
+ });
+ }
+});
+
+// curl -i --location --request POST 'http://localhost:54321/functions/v1/upstash-redis-ratelimit' \
+// --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjc4MDA4Mjk2LCJzdWIiOiJkZjQ3ZDU5My0zMjY1LTQ2OWEtOWI5OS1mZDk1OTdhOGU4YzciLCJlbWFpbCI6InRlc3RyQHRlc3Quc2ciLCJwaG9uZSI6IiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIiwicHJvdmlkZXJzIjpbImVtYWlsIl19LCJ1c2VyX21ldGFkYXRhIjp7fSwicm9sZSI6ImF1dGhlbnRpY2F0ZWQiLCJhYWwiOiJhYWwxIiwiYW1yIjpbeyJtZXRob2QiOiJwYXNzd29yZCIsInRpbWVzdGFtcCI6MTY3ODAwNDY5Nn1dLCJzZXNzaW9uX2lkIjoiMDNjMmFlYjUtMjk5MC00ZWU0LWIxYTYtZmU1Y2IwMGFhMjU3In0.WwqggrX34j0NINiulC9rsj-cd6HzV55ySxJkJlVsU4o' \
+// --header 'Content-Type: application/json' \
+// --data '{"name":"Functions"}'
diff --git a/package-lock.json b/package-lock.json
index d3c3ed3179d..f82efe01498 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -55,6 +55,7 @@
"clsx": "^1.2.1",
"common": "*",
"config": "*",
+ "framer-motion": "^6.5.1",
"github-slugger": "^2.0.0",
"gray-matter": "^4.0.3",
"isbot": "^3.6.5",
@@ -961,6 +962,12 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/@adobe/css-tools": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz",
+ "integrity": "sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==",
+ "dev": true
+ },
"node_modules/@algolia/autocomplete-core": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.8.3.tgz",
@@ -9743,6 +9750,54 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/@testing-library/jest-dom": {
+ "version": "5.16.5",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz",
+ "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==",
+ "dev": true,
+ "dependencies": {
+ "@adobe/css-tools": "^4.0.1",
+ "@babel/runtime": "^7.9.2",
+ "@types/testing-library__jest-dom": "^5.9.1",
+ "aria-query": "^5.0.0",
+ "chalk": "^3.0.0",
+ "css.escape": "^1.5.1",
+ "dom-accessibility-api": "^0.5.6",
+ "lodash": "^4.17.15",
+ "redent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
+ "node_modules/@testing-library/jest-dom/node_modules/chalk": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@testing-library/jest-dom/node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/@testing-library/react": {
"version": "12.1.5",
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz",
@@ -10462,6 +10517,15 @@
"integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==",
"dev": true
},
+ "node_modules/@types/testing-library__jest-dom": {
+ "version": "5.14.5",
+ "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz",
+ "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/jest": "*"
+ }
+ },
"node_modules/@types/uglify-js": {
"version": "3.17.1",
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.1.tgz",
@@ -15164,6 +15228,12 @@
"url": "https://github.com/sponsors/fb55"
}
},
+ "node_modules/css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
+ "dev": true
+ },
"node_modules/cssdb": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz",
@@ -39037,6 +39107,7 @@
},
"devDependencies": {
"@storybook/react": "^6.5.16",
+ "@testing-library/jest-dom": "^5.16.5",
"@types/common-tags": "^1.8.1",
"@types/react": "^17.0.37",
"@types/react-copy-to-clipboard": "^5.0.4",
@@ -40065,6 +40136,12 @@
}
},
"dependencies": {
+ "@adobe/css-tools": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz",
+ "integrity": "sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==",
+ "dev": true
+ },
"@algolia/autocomplete-core": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.8.3.tgz",
@@ -46428,6 +46505,45 @@
}
}
},
+ "@testing-library/jest-dom": {
+ "version": "5.16.5",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz",
+ "integrity": "sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==",
+ "dev": true,
+ "requires": {
+ "@adobe/css-tools": "^4.0.1",
+ "@babel/runtime": "^7.9.2",
+ "@types/testing-library__jest-dom": "^5.9.1",
+ "aria-query": "^5.0.0",
+ "chalk": "^3.0.0",
+ "css.escape": "^1.5.1",
+ "dom-accessibility-api": "^0.5.6",
+ "lodash": "^4.17.15",
+ "redent": "^3.0.0"
+ },
+ "dependencies": {
+ "chalk": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
+ "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "requires": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ }
+ }
+ }
+ },
"@testing-library/react": {
"version": "12.1.5",
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz",
@@ -47107,6 +47223,15 @@
"integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==",
"dev": true
},
+ "@types/testing-library__jest-dom": {
+ "version": "5.14.5",
+ "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz",
+ "integrity": "sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==",
+ "dev": true,
+ "requires": {
+ "@types/jest": "*"
+ }
+ },
"@types/uglify-js": {
"version": "3.17.1",
"resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.1.tgz",
@@ -50792,6 +50917,12 @@
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
"dev": true
},
+ "css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
+ "dev": true
+ },
"cssdb": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz",
@@ -51546,6 +51677,7 @@
"dotenv": "^16.0.3",
"ejs": "^3.1.8",
"eslint": "8.9.0",
+ "framer-motion": "^6.5.1",
"github-slugger": "^2.0.0",
"globby": "^12.0.2",
"gray-matter": "^4.0.3",
@@ -68105,6 +68237,7 @@
"@supabase/auth-helpers-react": "^0.3.1",
"@tailwindcss/forms": "^0.4.0",
"@tailwindcss/typography": "^0.5.0",
+ "@testing-library/jest-dom": "^5.16.5",
"@types/common-tags": "^1.8.1",
"@types/react": "^17.0.37",
"@types/react-copy-to-clipboard": "^5.0.4",
@@ -70213,4 +70346,4 @@
"integrity": "sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ=="
}
}
-}
\ No newline at end of file
+}
diff --git a/package.json b/package.json
index 5f5696c9a20..0a856ee8320 100644
--- a/package.json
+++ b/package.json
@@ -26,7 +26,8 @@
"docker:up": "cd docker && docker compose up",
"docker:down": "cd docker && docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml down --remove-orphans",
"docker:remove": "cd docker && docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml rm -vfs",
- "test": "jest",
+ "test:docs": "turbo run test --filter=docs",
+ "test:ui": "turbo run test --filter=ui",
"test:studio": "turbo run test --filter=studio",
"perf:kong": "ab -t 5 -c 20 -T application/json http://localhost:8000/",
"perf:meta": "ab -t 5 -c 20 -T application/json http://localhost:5555/tables",
diff --git a/packages/common/hooks/useBreakpoint.tsx b/packages/common/hooks/useBreakpoint.tsx
index 27e29f666d3..8e967f9e0e6 100644
--- a/packages/common/hooks/useBreakpoint.tsx
+++ b/packages/common/hooks/useBreakpoint.tsx
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'
import { useWindowSize } from 'react-use'
-const isBrowser = typeof window !== 'undefined'
+export const isBrowser = typeof window !== 'undefined'
export function useBreakpoint(breakpoint = 768) {
if (!isBrowser) return
diff --git a/packages/shared-data/package.json b/packages/shared-data/package.json
index 0658adfdbda..3a2165ffed7 100644
--- a/packages/shared-data/package.json
+++ b/packages/shared-data/package.json
@@ -3,9 +3,7 @@
"version": "0.0.0",
"description": "Data shared between Supabase apps",
"main": "./index.ts",
- "scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
- },
+ "scripts": {},
"author": "",
"license": "MIT"
}
diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx
index 4f2f9f9f11f..b6b1c095d9a 100644
--- a/packages/ui/index.tsx
+++ b/packages/ui/index.tsx
@@ -15,7 +15,7 @@ export * from './src/components/Icon'
export * from './src/components/Icon/IconContext'
export * from './src/components/GlassPanel'
export * from './src/components/IconPanel'
-// export * from './src/components/Image'
+export * from './src/components/Icon/IconBackground'
// DISPLAYS
@@ -30,6 +30,7 @@ export * from './src/components/CodeBlock'
// NAV
export * from './src/components/Menu'
+export * from './src/components/TextLink'
// OVERLAYS
diff --git a/packages/ui/jest.config.ts b/packages/ui/jest.config.ts
new file mode 100644
index 00000000000..1c86c7b4642
--- /dev/null
+++ b/packages/ui/jest.config.ts
@@ -0,0 +1,12 @@
+import type { Config } from '@jest/types'
+
+const config: Config.InitialOptions = {
+ preset: 'ts-jest',
+ setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'],
+ testEnvironment: 'jsdom',
+ // Restricting test file pattern to just tsx files since the js files need a babel preset to run
+ // and most likely won't pass now anyway - the tests were ported from the UI repo and are out of date
+ testRegex: '(.*\\.test.tsx$)',
+}
+
+export default config
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 7ec616945d6..38a83995b6c 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -6,9 +6,10 @@
"license": "MIT",
"sideEffects": false,
"scripts": {
+ "build-storybook": "build-storybook",
"build:icons": "node --experimental-modules internals/populate-icons.js",
"storybook": "start-storybook -p 6006",
- "build-storybook": "build-storybook"
+ "test": "jest"
},
"dependencies": {
"@headlessui/react": "^1.0.0",
@@ -48,6 +49,7 @@
},
"devDependencies": {
"@storybook/react": "^6.5.16",
+ "@testing-library/jest-dom": "^5.16.5",
"@types/common-tags": "^1.8.1",
"@types/react": "^17.0.37",
"@types/react-copy-to-clipboard": "^5.0.4",
diff --git a/packages/ui/src/components/Button/Button.test.js b/packages/ui/src/components/Button/Button.test.tsx
similarity index 59%
rename from packages/ui/src/components/Button/Button.test.js
rename to packages/ui/src/components/Button/Button.test.tsx
index 0b8dd6e4fe7..93e88e8650c 100644
--- a/packages/ui/src/components/Button/Button.test.js
+++ b/packages/ui/src/components/Button/Button.test.tsx
@@ -1,32 +1,41 @@
import React from 'react'
import { render, screen, fireEvent } from '@testing-library/react'
-import Button from './Button'
+import { Button, ButtonSize, ButtonType } from './Button'
+import defaultTheme from '../../lib/theme/defaultTheme'
+import Link from 'next/link'
-const SIZES = ['tiny', 'small', 'medium', 'large', 'xlarge']
-const TYPES = [
+const SIZES: ButtonSize[] = ['tiny', 'small', 'medium', 'large', 'xlarge']
+const TYPES: ButtonType[] = [
'primary',
'default',
'secondary',
+ 'alternative',
'outline',
'dashed',
'link',
'text',
+ 'danger',
+ 'warning',
]
describe('#Button', () => {
it('should render button correctly', () => {
const wrapper = render(Button)
+
expect(wrapper.getByText('Button')).toBeInTheDocument()
expect(() => wrapper.unmount()).not.toThrow()
})
it('should render different text', () => {
const wrapper = render(Button)
+
expect(screen.getByText('Button')).toBeInTheDocument()
+
wrapper.rerender(按钮)
+
expect(screen.getByText('按钮')).toBeInTheDocument()
})
-
+
it('should ignore events when disabled', () => {
const WrapperButton = () => {
const [state, setState] = React.useState('state1')
@@ -36,9 +45,12 @@ describe('#Button', () => {
)
}
+
render()
expect(screen.getByText('state1')).toBeInTheDocument()
+
fireEvent.click(screen.getByText('state1'))
+
expect(screen.getByText('state1')).toBeInTheDocument()
expect(screen.queryByText('state2')).not.toBeInTheDocument()
})
@@ -57,31 +69,44 @@ describe('#Button', () => {
expect(screen.queryByText('state2')).not.toBeInTheDocument()
})
- it('should have "w-full" class', async () => {
+ it('should have "w-full" class when block is true', async () => {
render(Button Block)
- expect(screen.queryByRole('button')).toHaveClass(
- 'sbui-btn sbui-btn-primary sbui-btn--w-full sbui-btn-container--shadow sbui-btn--tiny'
- )
+ expect(screen.queryByRole('button')).toHaveClass('w-full')
})
- it.each(TYPES)('should have "btn--%s" class', (type) => {
+ it.each(TYPES)(`should have %p class from theme`, (type) => {
+ const expected = defaultTheme.button.type[type]
+
render(Button Variant)
- if (type !== 'text' && type !== 'link') {
- expect(screen.queryByRole('button')).toHaveClass(
- `sbui-btn sbui-btn-${type} sbui-btn-container--shadow sbui-btn--tiny`
- )
- } else {
- expect(screen.queryByRole('button')).toHaveClass(
- `sbui-btn sbui-btn-${type} sbui-btn--tiny`
- )
- }
+ expect(screen.queryByRole('button')).toHaveClass(expected)
})
- it.each(SIZES)('should have "btn--%s" class', (size) => {
+ it.each(SIZES)('should have %p class from theme', (size) => {
+ const expected = defaultTheme.button.size[size]
+
render(Button)
- expect(screen.queryByRole('button')).toHaveClass(
- `sbui-btn sbui-btn-primary sbui-btn-container--shadow sbui-btn--${size}`
- )
+
+ expect(screen.queryByRole('button')).toHaveClass(expected)
+ })
+
+ it("shouldn't crash when wrapped with next/link", () => {
+ expect(() =>
+ render(
+
+ Button
+
+ )
+ ).not.toThrow()
+ })
+
+ it('should forward ref', () => {
+ const ref: React.MutableRefObject = {
+ current: null,
+ }
+
+ render(Button)
+
+ expect(ref.current).toBeInstanceOf(HTMLButtonElement)
})
})
diff --git a/packages/ui/src/components/Button/Button.tsx b/packages/ui/src/components/Button/Button.tsx
index f6f8108405d..145a9333ebb 100644
--- a/packages/ui/src/components/Button/Button.tsx
+++ b/packages/ui/src/components/Button/Button.tsx
@@ -1,4 +1,4 @@
-import React, { forwardRef, useRef, useImperativeHandle } from 'react'
+import React, { forwardRef } from 'react'
import { IconContext } from './../Icon/IconContext'
import { IconLoader } from './../Icon/icons/IconLoader'
@@ -15,19 +15,9 @@ export interface ButtonProps extends React.HTMLAttributes {
loading?: boolean
loadingCentered?: boolean
shadow?: boolean
- size?: 'tiny' | 'small' | 'medium' | 'large' | 'xlarge'
+ size?: ButtonSize
style?: React.CSSProperties
- type?:
- | 'primary'
- | 'default'
- | 'secondary'
- | 'alternative'
- | 'outline'
- | 'dashed'
- | 'link'
- | 'text'
- | 'danger'
- | 'warning'
+ type?: ButtonType
danger?: boolean
htmlType?: 'button' | 'submit' | 'reset'
ref?: any
@@ -40,14 +30,22 @@ export interface ButtonProps extends React.HTMLAttributes {
form?: string
}
+export type ButtonSize = 'tiny' | 'small' | 'medium' | 'large' | 'xlarge'
+export type ButtonType =
+ | 'primary'
+ | 'default'
+ | 'secondary'
+ | 'alternative'
+ | 'outline'
+ | 'dashed'
+ | 'link'
+ | 'text'
+ | 'danger'
+ | 'warning'
+
interface CustomButtonProps extends React.HTMLAttributes {}
-interface RefHandle {
- // container: () => HTMLElement | null
- button: () => HTMLButtonElement | null
-}
-
-export const Button = forwardRef(
+export const Button = forwardRef(
(
{
block,
@@ -75,16 +73,6 @@ export const Button = forwardRef(
}: ButtonProps,
ref
) => {
- // button ref
- // const containerRef = useRef(null)
- const buttonRef = useRef(null)
-
- useImperativeHandle(ref, () => ({
- button: () => {
- return buttonRef.current
- },
- }))
-
let __styles = styleHandler('button')
// styles
@@ -150,7 +138,7 @@ export const Button = forwardRef(
//
{
}
}, [])
+ // Detect an IME composition (so that we can ignore Enter keypress)
+ const [isImeComposing, setIsImeComposing] = useState(false)
+
return (
e.stopPropagation()}>
@@ -478,7 +481,7 @@ const AiCommand = () => {
{messages.length > 0 && !hasError &&
}
{
setSearch(e.target.value)
}
}}
+ onCompositionStart={() => setIsImeComposing(true)}
+ onCompositionEnd={() => setIsImeComposing(false)}
onKeyDown={(e) => {
switch (e.key) {
case 'Enter':
- if (!search) {
+ if (!search || isLoading || isResponding || isImeComposing) {
return
}
- if (isLoading || isResponding) {
- return
- }
- handleSubmit(search)
- return
+ return handleSubmit(search)
default:
return
}
diff --git a/packages/ui/src/components/Command/Command.icons.tsx b/packages/ui/src/components/Command/Command.icons.tsx
index 504f109f660..22e2a37a176 100644
--- a/packages/ui/src/components/Command/Command.icons.tsx
+++ b/packages/ui/src/components/Command/Command.icons.tsx
@@ -6,17 +6,25 @@ interface AiIconProps {
export const AiIcon = ({ className = 'text-brand-900' }: AiIconProps) => (
)
diff --git a/packages/ui/src/components/Command/Command.utils.tsx b/packages/ui/src/components/Command/Command.utils.tsx
index afdd277e26d..f7796e4a9b1 100644
--- a/packages/ui/src/components/Command/Command.utils.tsx
+++ b/packages/ui/src/components/Command/Command.utils.tsx
@@ -241,7 +241,7 @@ export const CommandItem = React.forwardRef
-
{children}
+
{children}
{badge}
diff --git a/packages/ui/src/components/Command/DocsSearch.tsx b/packages/ui/src/components/Command/DocsSearch.tsx
index 22e1cf54c0b..58107835dc1 100644
--- a/packages/ui/src/components/Command/DocsSearch.tsx
+++ b/packages/ui/src/components/Command/DocsSearch.tsx
@@ -49,6 +49,16 @@ export interface PageResult {
sections: PageSection[]
}
+const getDocsUrl = () => {
+ if (!process.env.NEXT_PUBLIC_SITE_URL || !process.env.NEXT_PUBLIC_LOCAL_SUPABASE) {
+ return 'https://supabase.com/docs'
+ }
+
+ const isLocal =
+ process.env.NEXT_PUBLIC_SITE_URL.includes('localhost') || process.env.NEXT_PUBLIC_LOCAL_SUPABASE
+ return isLocal ? 'http://localhost:3001/docs' : 'https://supabase.com/docs'
+}
+
const DocsSearch = () => {
const [results, setResults] = useState()
const [hasSearchError, setHasSearchError] = useState(false)
@@ -276,10 +286,11 @@ const DocsSearch = () => {
export default DocsSearch
export function formatPageUrl(page: PageResult) {
+ const docsUrl = getDocsUrl()
switch (page.type) {
case PageType.Markdown:
case PageType.Reference:
- return `/docs/${page.path}`
+ return `${docsUrl}${page.path}`
case PageType.GithubDiscussion:
return page.path
default:
diff --git a/packages/ui/src/components/Command/GenerateSQL/GenerateSQL.tsx b/packages/ui/src/components/Command/GenerateSQL/GenerateSQL.tsx
index 9a29d8bb6e8..f25aec4fcbb 100644
--- a/packages/ui/src/components/Command/GenerateSQL/GenerateSQL.tsx
+++ b/packages/ui/src/components/Command/GenerateSQL/GenerateSQL.tsx
@@ -74,6 +74,9 @@ const GenerateSQL = () => {
if (search) handleSubmit(search)
}, [])
+ // Detect an IME composition (so that we can ignore Enter keypress)
+ const [isImeComposing, setIsImeComposing] = useState(false)
+
const formatAnswer = (answer: string) => {
try {
return format(answer, {
@@ -210,7 +213,9 @@ const GenerateSQL = () => {
onKeyDown={(e) => {
switch (e.key) {
case 'Enter':
- if (!search || isLoading || isResponding) return
+ if (!search || isLoading || isResponding || isImeComposing) {
+ return
+ }
return handleSubmit(query)
default:
return
@@ -311,10 +316,14 @@ const GenerateSQL = () => {
setSearch(e.target.value)
}
}}
+ onCompositionStart={() => setIsImeComposing(true)}
+ onCompositionEnd={() => setIsImeComposing(false)}
onKeyDown={(e) => {
switch (e.key) {
case 'Enter':
- if (!search || isLoading || isResponding) return
+ if (!search || isLoading || isResponding || isImeComposing) {
+ return
+ }
return handleSubmit(search)
default:
return
diff --git a/packages/ui/src/components/GlassPanel/GlassPanel.tsx b/packages/ui/src/components/GlassPanel/GlassPanel.tsx
index 0275949ceab..4f84bb995da 100644
--- a/packages/ui/src/components/GlassPanel/GlassPanel.tsx
+++ b/packages/ui/src/components/GlassPanel/GlassPanel.tsx
@@ -49,6 +49,7 @@ const GlassPanel = ({
) : (
icon && {icon}
)}
- {title}
+ {title}
{children && {children}}
diff --git a/packages/ui/src/components/Icon/IconBackground.tsx b/packages/ui/src/components/Icon/IconBackground.tsx
new file mode 100644
index 00000000000..4b73319ba46
--- /dev/null
+++ b/packages/ui/src/components/Icon/IconBackground.tsx
@@ -0,0 +1,5 @@
+export const IconBackground: React.FC = (props) => (
+
+ {props.children}
+
+)
diff --git a/packages/ui/src/components/IconPanel/IconPanel.tsx b/packages/ui/src/components/IconPanel/IconPanel.tsx
index 6bf869052ff..91eed589bcd 100644
--- a/packages/ui/src/components/IconPanel/IconPanel.tsx
+++ b/packages/ui/src/components/IconPanel/IconPanel.tsx
@@ -1,11 +1,14 @@
import { useTheme } from 'common/Providers'
import * as React from 'react'
import { ChevronRight } from 'react-feather'
+import ReactTooltip from 'react-tooltip'
interface Props {
- title: string
+ title?: string
+ tooltip?: string
icon?: string | React.ReactNode
+ iconSize?: 'sm' | 'lg'
children?: React.ReactNode
background?: boolean
@@ -13,15 +16,19 @@ interface Props {
hasLightIcon?: boolean
showLink?: boolean
+ hideArrow?: boolean
}
const IconPanel = ({
title,
+ tooltip,
icon,
+ iconSize = 'sm',
children,
background = true,
hasLightIcon,
showLink = false,
+ hideArrow = false,
}: Props) => {
const { isDarkMode } = useTheme()
@@ -31,7 +38,7 @@ const IconPanel = ({
className={[
'relative',
'flex items-center justify-center shrink-0',
- 'h-10 w-10 rounded-lg',
+ iconSize === 'lg' ? 'h-16 w-16 rounded-lg' : 'h-10 w-10 rounded-lg',
'group',
'cursor-pointer',
'overflow-hidden',
@@ -48,44 +55,62 @@ const IconPanel = ({
}
return (
-
-
-
- {typeof icon === 'string' ? (
-
-
-
- ) : (
-
{icon}
- )}
-
-
-
{title}
-
+
+ {title &&
{title}
}
+ {!hideArrow && (
+
-
+ >
+
+
+ )}
+ {children &&
{children}}
+ {showLink &&
Learn more}
- {children &&
{children}}
- {showLink &&
Learn more}
-
-
-
+ >
+
+ {tooltip && (
+
+ )}
+ >
)
}
diff --git a/packages/ui/src/components/Input/Input.tsx b/packages/ui/src/components/Input/Input.tsx
index 2c7f96e0624..93a5b0fbd60 100644
--- a/packages/ui/src/components/Input/Input.tsx
+++ b/packages/ui/src/components/Input/Input.tsx
@@ -12,8 +12,10 @@ import { HIDDEN_PLACEHOLDER } from './../../lib/constants'
import styleHandler from '../../lib/theme/styleHandler'
import { useFormContext } from '../Form/FormContext'
-export interface Props extends Omit
, 'size'> {
+export interface Props
+ extends Omit, 'size' | 'onCopy'> {
copy?: boolean
+ onCopy?: () => void
defaultValue?: string | number
descriptionText?: string | React.ReactNode | undefined
disabled?: boolean
@@ -52,6 +54,7 @@ function Input({
layout,
onChange,
onBlur,
+ onCopy,
placeholder,
type = 'text',
value = undefined,
@@ -100,7 +103,7 @@ function Input({
// error = touched && touched[id] ? error : undefined
// }, [errors, touched])
- function onCopy(value: any) {
+ function _onCopy(value: any) {
navigator.clipboard.writeText(value)?.then(
function () {
/* clipboard successfully set */
@@ -108,6 +111,7 @@ function Input({
setTimeout(function () {
setCopyLabel('Copy')
}, 3000)
+ onCopy?.()
},
function () {
/* clipboard write failed */
@@ -152,6 +156,7 @@ function Input({
name={name}
onChange={onInputChange}
onBlur={handleBlurEvent}
+ onCopy={onCopy}
placeholder={placeholder}
ref={inputRef}
type={type}
@@ -164,7 +169,7 @@ function Input({
{error &&
}
{copy && !(reveal && hidden) ? (
-
} onClick={() => onCopy(value)}>
+
} onClick={() => _onCopy(value)}>
{copyLabel}
) : null}
@@ -182,7 +187,7 @@ function Input({
}
export interface TextAreaProps
- extends Omit
, 'size'> {
+ extends Omit, 'size' | 'onCopy'> {
descriptionText?: string
error?: string
icon?: any
@@ -197,6 +202,7 @@ export interface TextAreaProps
borderless?: boolean
validation?: (x: any) => void
copy?: boolean
+ onCopy?: () => void
actions?: React.ReactNode
}
@@ -224,13 +230,14 @@ function TextArea({
borderless = false,
validation,
copy = false,
+ onCopy,
actions,
...props
}: TextAreaProps) {
const [charLength, setCharLength] = useState(0)
const [copyLabel, setCopyLabel] = useState('Copy')
- function onCopy(value: any) {
+ function _onCopy(value: any) {
navigator.clipboard.writeText(value).then(
function () {
/* clipboard successfully set */
@@ -238,6 +245,7 @@ function TextArea({
setTimeout(function () {
setCopyLabel('Copy')
}, 3000)
+ onCopy?.()
},
function () {
/* clipboard write failed */
@@ -308,6 +316,7 @@ function TextArea({
placeholder={placeholder}
onChange={onInputChange}
onBlur={handleBlurEvent}
+ onCopy={onCopy}
value={value}
className={classes.join(' ')}
maxLength={limit}
@@ -323,7 +332,7 @@ function TextArea({
onCopy(value)}
+ onClick={() => _onCopy(value)}
icon={}
>
{copyLabel}
diff --git a/packages/ui/src/components/TextLink/TextLink.tsx b/packages/ui/src/components/TextLink/TextLink.tsx
new file mode 100644
index 00000000000..73d54f6136e
--- /dev/null
+++ b/packages/ui/src/components/TextLink/TextLink.tsx
@@ -0,0 +1,23 @@
+import { IconChevronRight } from 'ui'
+import Link from 'next/link'
+
+function TextLink({ url = '', label, ...props }: any) {
+ return (
+
+
+
+
{`${label} about ${url}`}
+
{label}
+
+
+
+
+
+
+ )
+}
+
+export default TextLink
diff --git a/packages/ui/src/components/TextLink/index.ts b/packages/ui/src/components/TextLink/index.ts
new file mode 100644
index 00000000000..077167d986d
--- /dev/null
+++ b/packages/ui/src/components/TextLink/index.ts
@@ -0,0 +1 @@
+export { default as TextLink } from './TextLink'
diff --git a/spec/cli_v1_commands.yaml b/spec/cli_v1_commands.yaml
index 1a9951d6628..c66b66318bb 100644
--- a/spec/cli_v1_commands.yaml
+++ b/spec/cli_v1_commands.yaml
@@ -1,7 +1,7 @@
clispec: '001'
info:
id: cli
- version: 1.50.13
+ version: 1.62.3
title: Supabase CLI
language: sh
source: https://github.com/supabase/cli
@@ -401,7 +401,24 @@ commands:
links: []
subcommands:
- supabase-test-db
+ - supabase-test-new
flags: []
+ - id: supabase-test-new
+ title: supabase test new
+ summary: Create a new test file
+ tags: []
+ links: []
+ usage: supabase test new [flags]
+ subcommands: []
+ flags:
+ - id: template
+ name: -t, --template <[ pgtap ]>
+ description: Template framework to generate.
+ default_value: pgtap
+ accepted_values:
+ - id: pgtap
+ name: pgtap
+ type: '[ pgtap ]'
- id: supabase-test-db
title: supabase test db
summary: Tests local database with pgTAP
@@ -458,9 +475,9 @@ commands:
usage: supabase stop [flags]
subcommands: []
flags:
- - id: backup
- name: --backup
- description: Backs up the current database before stopping.
+ - id: no-backup
+ name: --no-backup
+ description: Deletes all data volumes after stopping.
default_value: 'false'
- id: supabase-status
title: supabase status
@@ -663,7 +680,11 @@ commands:
links: []
usage: supabase secrets unset ...
subcommands: []
- flags: []
+ flags:
+ - id: project-ref
+ name: --project-ref
+ description: Project ref of the Supabase project.
+ default_value: ''
- id: supabase-secrets-set
title: supabase secrets set
summary: Set a secret(s) on Supabase
@@ -677,6 +698,10 @@ commands:
name: --env-file
description: Read secrets from a .env file.
default_value: ''
+ - id: project-ref
+ name: --project-ref
+ description: Project ref of the Supabase project.
+ default_value: ''
- id: supabase-secrets-list
title: supabase secrets list
summary: List all secrets on Supabase
@@ -685,7 +710,11 @@ commands:
links: []
usage: supabase secrets list
subcommands: []
- flags: []
+ flags:
+ - id: project-ref
+ name: --project-ref
+ description: Project ref of the Supabase project.
+ default_value: ''
- id: supabase-projects
title: supabase projects
summary: Manage Supabase projects
@@ -915,10 +944,19 @@ commands:
- supabase-migration-list
- supabase-migration-new
- supabase-migration-repair
+ - supabase-migration-up
+ flags: []
+ - id: supabase-migration-up
+ title: supabase migration up
+ summary: Apply pending migrations to local database
+ tags: []
+ links: []
+ usage: supabase migration up
+ subcommands: []
flags: []
- id: supabase-migration-repair
title: supabase migration repair
- summary: Repairs the migration history table
+ summary: Repair the migration history table
description: |2
Repairs the remote migration history table.
@@ -973,6 +1011,10 @@ commands:
usage: supabase migration repair [flags]
subcommands: []
flags:
+ - id: db-url
+ name: --db-url
+ description: connect using the specified database url
+ default_value: ''
- id: password
name: -p, --password
description: Password to your remote Postgres database.
@@ -989,10 +1031,6 @@ commands:
- id: reverted
name: reverted
type: '[ applied | reverted ]'
- - id: db-url
- name: --db-url
- description: connect using the specified database url
- default_value: ''
- id: supabase-migration-new
title: supabase migration new
summary: Create an empty migration script
@@ -1018,11 +1056,7 @@ commands:
links: []
usage: supabase migration new
subcommands: []
- flags:
- - id: db-url
- name: --db-url
- description: connect using the specified database url
- default_value: ''
+ flags: []
- id: supabase-migration-list
title: supabase migration list
summary: List local and remote migrations
@@ -1060,14 +1094,14 @@ commands:
usage: supabase migration list [flags]
subcommands: []
flags:
- - id: password
- name: -p, --password
- description: Password to your remote Postgres database.
- default_value: ''
- id: db-url
name: --db-url
description: connect using the specified database url
default_value: ''
+ - id: password
+ name: -p, --password
+ description: Password to your remote Postgres database.
+ default_value: ''
- id: supabase-login
title: supabase login
summary: Authenticate using an access token
@@ -1176,6 +1210,7 @@ commands:
- local-dev
links: []
subcommands:
+ - supabase-gen-keys
- supabase-gen-types
flags: []
- id: supabase-gen-types
@@ -1216,6 +1251,44 @@ commands:
name: --schema
description: Schemas to generate types for.
default_value: '[]'
+ - id: supabase-gen-keys
+ title: supabase gen keys
+ summary: Generate keys for preview branch
+ tags: []
+ links: []
+ usage: supabase gen keys [flags]
+ subcommands: []
+ flags:
+ - id: output
+ name: -o, --output <[ env | json | toml | yaml ]>
+ description: Output format of key variables.
+ default_value: env
+ accepted_values:
+ - id: env
+ name: env
+ type: '[ env | json | toml | yaml ]'
+ - id: json
+ name: json
+ type: '[ env | json | toml | yaml ]'
+ - id: toml
+ name: toml
+ type: '[ env | json | toml | yaml ]'
+ - id: yaml
+ name: yaml
+ type: '[ env | json | toml | yaml ]'
+ - id: override-name
+ name: --override-name
+ description: Override specific variable names.
+ default_value: '[]'
+ - id: project-ref
+ name: --project-ref
+ description: Project ref of the Supabase project.
+ default_value: ''
+ - id: experimental
+ name: --experimental
+ description: enable experimental features
+ required: true
+ default_value: 'false'
- id: supabase-functions
title: supabase functions
summary: Manage Supabase Edge functions
@@ -1285,10 +1358,6 @@ commands:
name: --import-map
description: Path to import map file.
default_value: ''
- - id: legacy-bundle
- name: --legacy-bundle
- description: Use legacy bundling mechanism.
- default_value: 'false'
- id: no-verify-jwt
name: --no-verify-jwt
description: Disable JWT verification for the Function.
@@ -1474,9 +1543,13 @@ commands:
Finished supabase db reset on branch main.
tags: []
links: []
- usage: supabase db reset
+ usage: supabase db reset [flags]
subcommands: []
flags:
+ - id: linked
+ name: --linked
+ description: Resets the linked project to current migrations.
+ default_value: 'false'
- id: db-url
name: --db-url
description: connect using the specified database url
@@ -1492,6 +1565,13 @@ commands:
- id: supabase-db-remote-commit
title: supabase db remote commit
summary: Commit remote changes as a new migration
+ description: |2
+
+ Pulls schema changes from a remote database and inserts a new row into the migration history table.
+
+ Requires your local project to be linked to a remote database by running `supabase link`. For self-hosted databases, you can pass in the connection parameters using `--db-url` flag.
+
+ If no entries exist in the migration history table, `pg_dump` will be used to capture all contents of the remote schemas you have created. After that, a new record will be inserted into the migration history table. Subsequent runs of this command will only diff schema changes against the remote database similar to `db diff --linked`.
tags: []
links: []
usage: supabase db remote commit
@@ -1614,6 +1694,10 @@ commands:
- id: error
name: error
type: '[ warning | error ]'
+ - id: linked
+ name: --linked
+ description: Lints the linked project for schema errors.
+ default_value: 'false'
- id: schema
name: -s, --schema
description: List of schema to include.
@@ -1666,6 +1750,10 @@ commands:
name: -f, --file
description: File path to save the dumped contents.
default_value: ''
+ - id: keep-comments
+ name: --keep-comments
+ description: Keeps commented lines from pg_dump output.
+ default_value: 'false'
- id: password
name: -p, --password
description: Password to your remote Postgres database.
@@ -1740,7 +1828,7 @@ commands:
default_value: ''
- id: linked
name: --linked
- description: Diffs local schema against linked project.
+ description: Diffs local schema against the linked project.
default_value: 'false'
- id: schema
name: -s, --schema
diff --git a/spec/common-cli-sections.json b/spec/common-cli-sections.json
index b624e7ec6c5..a1dd8615255 100644
--- a/spec/common-cli-sections.json
+++ b/spec/common-cli-sections.json
@@ -70,6 +70,12 @@
"title": "Run tests (pgTAP)",
"slug": "supabase-test-db",
"type": "cli-command"
+ },
+ {
+ "id": "supabase-test-new",
+ "title": "Create a new test",
+ "slug": "supabase-test-new",
+ "type": "cli-command"
}
]
},
@@ -83,6 +89,12 @@
"slug": "supabase-gen",
"type": "cli-command"
},
+ {
+ "id": "supabase-gen-keys",
+ "title": "Generate keys (Preview branch)",
+ "slug": "supabase-gen-keys",
+ "type": "cli-command"
+ },
{
"id": "supabase-gen-types",
"title": "Generate types (Postgres schema)",
@@ -178,6 +190,12 @@
"title": "Repair migration history table",
"slug": "supabase-migration-repair",
"type": "cli-command"
+ },
+ {
+ "id": "supabase-migration-up",
+ "title": "Apply pending migrations locally",
+ "slug": "supabase-migration-up",
+ "type": "cli-command"
}
]
},
diff --git a/spec/supabase_dart_v1.yml b/spec/supabase_dart_v1.yml
index 1f2489ba4be..74d497ad6c4 100644
--- a/spec/supabase_dart_v1.yml
+++ b/spec/supabase_dart_v1.yml
@@ -912,9 +912,17 @@ functions:
notes: |
- `stream()` will emit the initial data as well as any further change on the database as `Stream` of `List
)
diff --git a/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx b/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx
index 3dca2403e09..5e40307f65b 100644
--- a/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx
+++ b/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx
@@ -6,6 +6,7 @@ import { Badge, Button, Loading, Listbox, IconUser, Modal, IconAlertCircle, Icon
import { Member } from 'types'
import { useStore } from 'hooks'
+import useProfile from 'hooks/misc/useProfile'
import { useParams } from 'common/hooks'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
@@ -14,7 +15,6 @@ import MemberActions from './MemberActions'
import RolesHelperModal from './RolesHelperModal/RolesHelperModal'
import { getRolesManagementPermissions } from './TeamSettings.utils'
import { isInviteExpired, getUserDisplayName } from '../Organization.utils'
-import { useProfileQuery } from 'data/profile/profile-query'
import { useOrganizationRolesQuery } from 'data/organizations/organization-roles-query'
import { useOrganizationDetailQuery } from 'data/organizations/organization-detail-query'
import { useOrganizationMemberUpdateMutation } from 'data/organizations/organization-member-update-mutation'
@@ -32,7 +32,7 @@ const MembersView = ({ searchString }: MembersViewProps) => {
const { ui } = useStore()
const { slug } = useParams()
- const { data: profile } = useProfileQuery()
+ const { data: profile } = useProfile()
const { data: detailData, isLoading: isLoadingOrgDetails } = useOrganizationDetailQuery({ slug })
const { data: rolesData, isLoading: isLoadingRoles } = useOrganizationRolesQuery({ slug })
const { mutate: updateOrganizationMember, isLoading } = useOrganizationMemberUpdateMutation({
diff --git a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx
index bb5f155102d..04cb1a953a2 100644
--- a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx
+++ b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx
@@ -4,10 +4,10 @@ import { Button, Input, IconSearch } from 'ui'
import * as Tooltip from '@radix-ui/react-tooltip'
import { useStore } from 'hooks'
+import useProfile from 'hooks/misc/useProfile'
import { useParams } from 'common/hooks'
import { post } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
-import { useProfileQuery } from 'data/profile/profile-query'
import InviteMemberButton from './InviteMemberButton'
import MembersView from './MembersView'
import { getRolesManagementPermissions, hasMultipleOwners } from './TeamSettings.utils'
@@ -19,7 +19,7 @@ const TeamSettings = () => {
const { ui } = useStore()
const { slug } = useParams()
- const { data: profile } = useProfileQuery()
+ const { data: profile } = useProfile()
const isOwner = ui.selectedOrganization?.is_owner
const { data: detailData } = useOrganizationDetailQuery({ slug })
diff --git a/studio/components/interfaces/Reports/Reports.constants.ts b/studio/components/interfaces/Reports/Reports.constants.ts
index 718250d0cc4..914c8a068ae 100644
--- a/studio/components/interfaces/Reports/Reports.constants.ts
+++ b/studio/components/interfaces/Reports/Reports.constants.ts
@@ -278,3 +278,4 @@ select
}
export const DATETIME_FORMAT = 'MMM D, ha'
+export const DATETIME_SECOND_FORMAT = 'MMM D, ha'
diff --git a/studio/components/interfaces/Reports/Reports.tsx b/studio/components/interfaces/Reports/Reports.tsx
index 4b41c46826b..f4215efde7f 100644
--- a/studio/components/interfaces/Reports/Reports.tsx
+++ b/studio/components/interfaces/Reports/Reports.tsx
@@ -17,11 +17,11 @@ import {
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions } from 'hooks'
+import useProfile from 'hooks/misc/useProfile'
import { useParams } from 'common/hooks'
import { uuidv4 } from 'lib/helpers'
import { METRIC_CATEGORIES, METRICS, TIME_PERIODS_REPORTS } from 'lib/constants'
import { useProjectContentStore } from 'stores/projectContentStore'
-import { useProfileQuery } from 'data/profile/profile-query'
import Loading from 'components/ui/Loading'
import DateRangePicker from 'components/to-be-cleaned/DateRangePicker'
import NoPermission from 'components/ui/NoPermission'
@@ -33,7 +33,7 @@ const DEFAULT_CHART_ROW_COUNT = 4
const Reports = () => {
const { id, ref } = useParams()
- const { data: profile } = useProfileQuery()
+ const { data: profile } = useProfile()
const [report, setReport] = useState()
diff --git a/studio/components/interfaces/Reports/Reports.types.ts b/studio/components/interfaces/Reports/Reports.types.ts
index 201f62f712b..a13240a5125 100644
--- a/studio/components/interfaces/Reports/Reports.types.ts
+++ b/studio/components/interfaces/Reports/Reports.types.ts
@@ -7,20 +7,6 @@ export enum Presets {
QUERY_PERFORMANCE = 'query_performance',
}
-export interface QueryDataBase {
- isLoading: boolean
- error: string
-}
-export interface DbQueryData extends QueryDataBase {
- data: T[]
- params: BaseReportParams
- logData?: never
-}
-export interface DbQueryHandler {
- runQuery: () => void
- setParams?: never
- changeQuery?: never
-}
export type MetaQueryResponse = any & { error: ResponseError }
export type BaseReportParams = typeof DEFAULT_QUERY_PARAMS & { sql?: string } & unknown
diff --git a/studio/components/interfaces/Reports/Reports.utils.tsx b/studio/components/interfaces/Reports/Reports.utils.tsx
index 668031dd552..3182492f658 100644
--- a/studio/components/interfaces/Reports/Reports.utils.tsx
+++ b/studio/components/interfaces/Reports/Reports.utils.tsx
@@ -1,12 +1,6 @@
-import useDbQuery from 'hooks/analytics/useDbQuery'
-import useLogsQuery, { LogsQueryData, LogsQueryHandlers } from 'hooks/analytics/useLogsQuery'
-import {
- BaseQueries,
- DbQueryData,
- DbQueryHandler,
- PresetConfig,
- ReportQuery,
-} from './Reports.types'
+import useDbQuery, { DbQueryHook } from 'hooks/analytics/useDbQuery'
+import useLogsQuery, { LogsQueryHook } from 'hooks/analytics/useLogsQuery'
+import { BaseQueries, PresetConfig, ReportQuery } from './Reports.types'
/**
* Converts a query params string to an object
@@ -16,7 +10,7 @@ export const queryParamsToObject = (params: string) => {
}
// generate hooks based on preset config
-type PresetHookResult = [LogsQueryData | DbQueryData, LogsQueryHandlers | DbQueryHandler]
+type PresetHookResult = LogsQueryHook | DbQueryHook
type PresetHooks = Record PresetHookResult>
export const queriesFactory = (
diff --git a/studio/components/interfaces/SQLEditor/MonacoEditor.tsx b/studio/components/interfaces/SQLEditor/MonacoEditor.tsx
index 5cbfc5b4b40..e062680c8e3 100644
--- a/studio/components/interfaces/SQLEditor/MonacoEditor.tsx
+++ b/studio/components/interfaces/SQLEditor/MonacoEditor.tsx
@@ -1,19 +1,21 @@
import Editor, { Monaco, OnMount } from '@monaco-editor/react'
import { timeout } from 'lib/helpers'
-import { useEffect, useRef } from 'react'
+import { MutableRefObject, useEffect, useRef } from 'react'
import { useSqlEditorStateSnapshot } from 'state/sql-editor'
+export type IStandaloneCodeEditor = Parameters[0]
+
export type MonacoEditorProps = {
id: string
+ editorRef: MutableRefObject
isExecuting: boolean
- executeQuery: (overrideSql?: string) => void
+ executeQuery: () => void
}
-const MonacoEditor = ({ id, isExecuting, executeQuery }: MonacoEditorProps) => {
+const MonacoEditor = ({ id, editorRef, isExecuting, executeQuery }: MonacoEditorProps) => {
const snap = useSqlEditorStateSnapshot({ sync: true })
const snippet = snap.snippets[id]
- const editorRef = useRef(null)
const monacoRef = useRef(null)
const executeQueryRef = useRef(executeQuery)
executeQueryRef.current = executeQuery
@@ -22,7 +24,9 @@ const MonacoEditor = ({ id, isExecuting, executeQuery }: MonacoEditorProps) => {
if (!editorRef.current || !monacoRef.current) return
const model = editorRef.current.getModel()
- monacoRef.current.editor.setModelMarkers(model, 'owner', [])
+ if (model !== null) {
+ monacoRef.current.editor.setModelMarkers(model, 'owner', [])
+ }
}, [])
useEffect(() => {
@@ -49,13 +53,7 @@ const MonacoEditor = ({ id, isExecuting, executeQuery }: MonacoEditorProps) => {
contextMenuGroupId: 'operation',
contextMenuOrder: 0,
run: () => {
- if (isExecuting) return
-
- const selectedValue = (editorRef?.current)
- .getModel()
- .getValueInRange(editorRef?.current?.getSelection())
-
- executeQueryRef.current?.(selectedValue || editorRef?.current?.getValue())
+ executeQueryRef.current()
},
})
diff --git a/studio/components/interfaces/SQLEditor/RenameQueryModal.tsx b/studio/components/interfaces/SQLEditor/RenameQueryModal.tsx
index ff43f2b67f9..fcd49c05232 100644
--- a/studio/components/interfaces/SQLEditor/RenameQueryModal.tsx
+++ b/studio/components/interfaces/SQLEditor/RenameQueryModal.tsx
@@ -1,7 +1,6 @@
import { useParams } from 'common'
-import { useContentUpdateMutation } from 'data/content/content-update-mutation'
import { useStore } from 'hooks'
-import { sqlEditorState } from 'state/sql-editor'
+import { useSqlEditorStateSnapshot } from 'state/sql-editor'
import { Button, Form, Input, Modal } from 'ui'
export interface RenameQueryModalProps {
@@ -14,13 +13,7 @@ export interface RenameQueryModalProps {
const RenameQueryModal = ({ snippet, visible, onCancel, onComplete }: RenameQueryModalProps) => {
const { ui } = useStore()
const { ref } = useParams()
- const { mutateAsync: updateContent } = useContentUpdateMutation({
- onSuccess(data) {
- if (data.content.id) {
- sqlEditorState.snippets[data.content.id].snippet.name = data.content.name
- }
- },
- })
+ const snap = useSqlEditorStateSnapshot()
const { id, name, description } = snippet
@@ -36,7 +29,7 @@ const RenameQueryModal = ({ snippet, visible, onCancel, onComplete }: RenameQuer
setSubmitting(true)
try {
- await updateContent({ projectRef: ref, id, content: values })
+ snap.renameSnippet(id, values.name, values.description)
if (onComplete) onComplete()
return Promise.resolve()
} catch (error: any) {
diff --git a/studio/components/interfaces/SQLEditor/SQLEditor.tsx b/studio/components/interfaces/SQLEditor/SQLEditor.tsx
index c7a1296c987..f9e9d0c71b7 100644
--- a/studio/components/interfaces/SQLEditor/SQLEditor.tsx
+++ b/studio/components/interfaces/SQLEditor/SQLEditor.tsx
@@ -1,14 +1,12 @@
import { useParams } from 'common'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
-import { useCallback } from 'react'
+import { useCallback, useRef } from 'react'
import Split from 'react-split'
import { getSqlEditorStateSnapshot, useSqlEditorStateSnapshot } from 'state/sql-editor'
-import useLatest from 'hooks/misc/useLatest'
-import MonacoEditor from './MonacoEditor'
-import UtilityPanel from './UtilityPanel/UtilityPanel'
import { useExecuteSqlMutation } from 'data/sql/execute-sql-mutation'
-
-export interface SQLEditorProps {}
+import useLatest from 'hooks/misc/useLatest'
+import MonacoEditor, { IStandaloneCodeEditor } from './MonacoEditor'
+import UtilityPanel from './UtilityPanel/UtilityPanel'
const SQLEditor = () => {
const { ref, id } = useParams()
@@ -38,22 +36,26 @@ const SQLEditor = () => {
if (id) snap.setSplitSizes(id, sizes)
}, [])
- const executeQuery = useCallback(
- (overrideSql?: string) => {
- // use the latest state
- const state = getSqlEditorStateSnapshot()
- const snippet = idRef.current && state.snippets[idRef.current]
+ const editorRef = useRef(null)
- if (project && snippet && !isExecuting) {
- execute?.({
- projectRef: project.ref,
- connectionString: project.connectionString,
- sql: overrideSql ?? snippet.snippet.content.sql,
- })
- }
- },
- [isExecuting, project]
- )
+ const executeQuery = useCallback(() => {
+ // use the latest state
+ const state = getSqlEditorStateSnapshot()
+ const snippet = idRef.current && state.snippets[idRef.current]
+
+ if (project && snippet && !isExecuting && editorRef.current !== null) {
+ const editor = editorRef.current
+ const selection = editor.getSelection()
+ const selectedValue = selection ? editor.getModel()?.getValueInRange(selection) : undefined
+ const overrideSql = selectedValue || editorRef.current?.getValue()
+
+ execute({
+ projectRef: project.ref,
+ connectionString: project.connectionString,
+ sql: overrideSql ?? snippet.snippet.content.sql,
+ })
+ }
+ }, [isExecuting, project])
return (
@@ -72,7 +74,12 @@ const SQLEditor = () => {
{isLoading ? (
Loading...
) : (
-
+
)}
@@ -80,7 +87,7 @@ const SQLEditor = () => {
{isLoading ? (
Loading...
) : (
-
+
)}
diff --git a/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx b/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx
index 86f850fbce4..d1e950982a9 100644
--- a/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx
+++ b/studio/components/interfaces/SQLEditor/SQLTemplates/SQLTemplates.tsx
@@ -4,7 +4,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
import Telemetry from 'lib/telemetry'
import { checkPermissions, useStore } from 'hooks'
-import { useProfileQuery } from 'data/profile/profile-query'
+import useProfile from 'hooks/misc/useProfile'
import { SQL_TEMPLATES } from 'components/interfaces/SQLEditor/SQLEditor.constants'
import SQLCard from './SQLCard'
import { createSqlSnippetSkeleton } from '../SQLEditor.utils'
@@ -18,7 +18,7 @@ const SQLTemplates = observer(() => {
const { ui } = useStore()
const { ref } = useParams()
const router = useRouter()
- const { data: profile } = useProfileQuery()
+ const { data: profile } = useProfile()
const [sql, quickStart] = partition(SQL_TEMPLATES, { type: 'template' })
const telemetryProps = useTelemetryProps()
diff --git a/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx b/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx
index c3c92adcbdc..4523ff57eca 100644
--- a/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx
+++ b/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityActions.tsx
@@ -8,7 +8,7 @@ import { detectOS } from 'lib/helpers'
export type UtilityActionsProps = {
id: string
isExecuting?: boolean
- executeQuery?: (overrideSql?: string) => void
+ executeQuery: () => void
}
const UtilityActions = ({ id, isExecuting = false, executeQuery }: UtilityActionsProps) => {
@@ -20,7 +20,7 @@ const UtilityActions = ({ id, isExecuting = false, executeQuery }: UtilityAction
{IS_PLATFORM &&
}
executeQuery?.()}
+ onClick={() => executeQuery()}
disabled={isExecuting}
loading={isExecuting}
type="default"
diff --git a/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityPanel.tsx b/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityPanel.tsx
index c7f1f8c0e5b..3cd087502f9 100644
--- a/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityPanel.tsx
+++ b/studio/components/interfaces/SQLEditor/UtilityPanel/UtilityPanel.tsx
@@ -5,7 +5,7 @@ import UtilityTabResults from './UtilityTabResults'
export type UtilityPanelProps = {
id: string
isExecuting?: boolean
- executeQuery?: (overrideSql?: string) => void
+ executeQuery: () => void
}
const UtilityPanel = ({ id, isExecuting, executeQuery }: UtilityPanelProps) => {
diff --git a/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.tsx b/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.tsx
index f99c80dc922..f88e0a9704f 100644
--- a/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.tsx
+++ b/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.tsx
@@ -1,14 +1,19 @@
import { FC } from 'react'
+import { useRouter } from 'next/router'
import { Input, Tabs } from 'ui'
import { pluckObjectFields } from 'lib/helpers'
import { useProjectSettingsQuery } from 'data/config/project-settings-query'
+import { useTelemetryProps } from 'common'
+import Telemetry from 'lib/telemetry'
import Panel from 'components/ui/Panel'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import ResetDbPassword from './ResetDbPassword'
const DatabaseSettings: FC = ({ projectRef }) => {
const { data, isLoading, isError } = useProjectSettingsQuery({ projectRef })
+ const router = useRouter()
+ const telemetryProps = useTelemetryProps()
if (isError) {
return (
@@ -102,6 +107,16 @@ const DatabaseSettings: FC = ({ projectRef }) => {
const DB_FIELDS = ['db_host', 'db_name', 'db_port', 'db_user', 'inserted_at']
const connectionInfo = pluckObjectFields(project, DB_FIELDS)
+ const handleCopy = (labelValue?: string) =>
+ Telemetry.sendEvent(
+ {
+ category: 'settings',
+ action: 'copy_connection_string',
+ label: labelValue ? labelValue : '',
+ },
+ telemetryProps,
+ router
+ )
const uriConnString =
`postgresql://${connectionInfo.db_user}:[YOUR-PASSWORD]@` +
`${connectionInfo.db_host}:${connectionInfo.db_port.toString()}` +
@@ -136,6 +151,9 @@ const DatabaseSettings: FC = ({ projectRef }) => {
disabled
value={connectionInfo.db_host}
label="Host"
+ onCopy={() => {
+ handleCopy('Host')
+ }}
/>
= ({ projectRef }) => {
-
+ {
+ handleCopy('PSQL')
+ }}
+ />
-
+ {
+ handleCopy('URI')
+ }}
+ />
-
+ {
+ handleCopy('Golang')
+ }}
+ />
@@ -216,6 +258,9 @@ const DatabaseSettings: FC = ({ projectRef }) => {
}:${connectionInfo.db_port.toString()}` +
`/${connectionInfo.db_name}?user=${connectionInfo.db_user}&password=[YOUR-PASSWORD]`
}
+ onCopy={() => {
+ handleCopy('JDBC')
+ }}
/>
@@ -229,15 +274,34 @@ const DatabaseSettings: FC = ({ projectRef }) => {
`Server=${connectionInfo.db_host};Port=${connectionInfo.db_port.toString()};` +
`Database=${connectionInfo.db_name}`
}
+ onCopy={() => {
+ handleCopy('.NET')
+ }}
/>
-
+ {
+ handleCopy('Nodejs')
+ }}
+ />
-
+ {
+ handleCopy('PHP')
+ }}
+ />
@@ -250,6 +314,9 @@ const DatabaseSettings: FC = ({ projectRef }) => {
` host=${connectionInfo.db_host} port=${connectionInfo.db_port.toString()}` +
` database=${connectionInfo.db_name}`
}
+ onCopy={() => {
+ handleCopy('Python')
+ }}
/>
diff --git a/studio/components/interfaces/Settings/Logs/LogColumnRenderers/AuthColumnRenderer.tsx b/studio/components/interfaces/Settings/Logs/LogColumnRenderers/AuthColumnRenderer.tsx
index 62994a7bff5..68b56b161d2 100644
--- a/studio/components/interfaces/Settings/Logs/LogColumnRenderers/AuthColumnRenderer.tsx
+++ b/studio/components/interfaces/Settings/Logs/LogColumnRenderers/AuthColumnRenderer.tsx
@@ -1,6 +1,4 @@
-import { tryParseJson } from 'lib/helpers'
import { PreviewLogData } from '..'
-import { AuthEventParsed } from '../LogSelectionRenderers/AuthSelectionRenderer'
import {
RowLayout,
SeverityFormatter,
@@ -10,17 +8,18 @@ import {
export default [
{
- formatter: (data: { row: PreviewLogData }) => {
- const parsed: AuthEventParsed | undefined = tryParseJson(data.row.event_message.trim())
-
+ formatter: (data: {
+ row: PreviewLogData & { level: string; msg: string | null; status: number; path: string }
+ }) => {
return (
- {parsed?.level && }
+ {data.row.level && }
diff --git a/studio/components/interfaces/Settings/Logs/LogSelection.tsx b/studio/components/interfaces/Settings/Logs/LogSelection.tsx
index 0fba23d8f21..a7a9d228670 100644
--- a/studio/components/interfaces/Settings/Logs/LogSelection.tsx
+++ b/studio/components/interfaces/Settings/Logs/LogSelection.tsx
@@ -35,7 +35,7 @@ const LogSelection: FC = ({
queryType,
params = {},
}) => {
- const [{ logData: fullLog, isLoading }] = useSingleLog(
+ const { logData: fullLog, isLoading } = useSingleLog(
projectRef,
queryType,
params,
diff --git a/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/AuthSelectionRenderer.tsx b/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/AuthSelectionRenderer.tsx
index 1155d4aedc6..9f80f0fb3d5 100644
--- a/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/AuthSelectionRenderer.tsx
+++ b/studio/components/interfaces/Settings/Logs/LogSelectionRenderers/AuthSelectionRenderer.tsx
@@ -1,4 +1,3 @@
-import { tryParseJson } from 'lib/helpers'
import { PreviewLogData } from '..'
import { LOGS_TAILWIND_CLASSES } from '../Logs.constants'
import {
@@ -9,51 +8,44 @@ import {
SeverityFormatter,
} from '../LogsFormatters'
-export interface AuthEventParsed extends Record {
- component: string
- error: string
- method: string
- level: string
- msg: string
- path: string
- status: number
-}
const AuthSelectionRenderer = ({ log }: { log: PreviewLogData }) => {
- const parsed: AuthEventParsed | undefined = tryParseJson(log.event_message.trim())
-
return (
Event Message
- {parsed?.msg || log.event_message}
+ {log.metadata?.msg || log.event_message}
- {parsed?.status && (
+ {log.metadata?.status && (
}
+ value={String(log.metadata?.status)}
+ valueRender={
}
/>
)}
- {parsed?.level && (
+ {log.metadata?.level && (
}
+ value={log.metadata?.level}
+ valueRender={
}
/>
)}
- {parsed?.path &&
}
- {parsed?.error &&
}
+ {log.metadata?.path && (
+
+ )}
+ {log.metadata?.error && (
+
+ )}
diff --git a/studio/components/interfaces/Settings/Logs/LogTable.tsx b/studio/components/interfaces/Settings/Logs/LogTable.tsx
index c0f430681c7..99900a05372 100644
--- a/studio/components/interfaces/Settings/Logs/LogTable.tsx
+++ b/studio/components/interfaces/Settings/Logs/LogTable.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useState, useMemo } from 'react'
+import { useEffect, useState, useMemo, useCallback } from 'react'
import { Alert, Button, IconEye, IconEyeOff } from 'ui'
import DataGrid, { Row, RowRendererProps } from '@supabase/react-data-grid'
@@ -15,6 +15,7 @@ import DefaultErrorRenderer from './LogsErrorRenderers/DefaultErrorRenderer'
import FunctionsLogsColumnRender from './LogColumnRenderers/FunctionsLogsColumnRender'
import FunctionsEdgeColumnRender from './LogColumnRenderers/FunctionsEdgeColumnRender'
import AuthColumnRenderer from './LogColumnRenderers/AuthColumnRenderer'
+import { isEqual } from 'lodash'
interface Props {
data?: Array
@@ -59,7 +60,6 @@ const LogTable = ({
onHistogramToggle,
isHistogramShowing,
isLoading,
- showDownload,
error,
projectRef,
params,
@@ -144,8 +144,10 @@ const LogTable = ({
}, [stringData])
useEffect(() => {
- if (!hasId || data === null) return
- if (focusedLog && !(focusedLog.id in logMap)) {
+ if (!data) return
+ const found = data.find((datum) => isEqual(datum, focusedLog))
+ if (!found) {
+ // close selection panel if not found in dataset
setFocusedLog(null)
}
}, [stringData])
@@ -161,14 +163,12 @@ const LogTable = ({
}
}, [stringData])
- const RowRenderer = (props: RowRendererProps) => {
- return (
-
- )
- }
+ const RowRenderer = useCallback(
+ (props: RowRendererProps) => (
+
+ ),
+ []
+ )
const LogsExplorerTableHeader = () => (
@@ -268,10 +268,11 @@ const LogTable = ({
`}
rowHeight={40}
headerRowHeight={queryType ? 0 : 28}
- onSelectedCellChange={({ idx, rowIdx }) => {
+ onSelectedCellChange={({ rowIdx }) => {
if (!hasId) return
setFocusedLog(data[rowIdx] as LogData)
}}
+ selectedRows={new Set([])}
noRowsFallback={
!isLoading ? (
@@ -282,15 +283,20 @@ const LogTable = ({
}
columns={columns as any}
rowClass={(row: LogData) =>
- row.id === focusedLog?.id ? '!bg-scale-400 rdg-row--focused' : 'cursor-pointer'
+ [
+ 'font-mono tracking-tight',
+ isEqual(row, focusedLog)
+ ? '!bg-scale-800 rdg-row--focused'
+ : ' !bg-scale-200 hover:!bg-scale-300 cursor-pointer',
+ ].join(' ')
}
rows={logDataRows}
rowKeyGetter={(r) => {
- if (!hasId) return Object.keys(r)[0]
+ if (!hasId) return JSON.stringify(r)
const row = r as LogData
return row.id
}}
- onRowClick={(r) => setFocusedLog(r)}
+ onRowClick={setFocusedLog}
rowRenderer={RowRenderer}
/>
{logDataRows.length > 0 ? (
diff --git a/studio/components/interfaces/Settings/Logs/Logs.constants.ts b/studio/components/interfaces/Settings/Logs/Logs.constants.ts
index 4801a389379..f798a345554 100644
--- a/studio/components/interfaces/Settings/Logs/Logs.constants.ts
+++ b/studio/components/interfaces/Settings/Logs/Logs.constants.ts
@@ -278,19 +278,19 @@ export const SQL_FILTER_TEMPLATES: any = {
},
auth_logs: {
..._SQL_FILTER_COMMON,
- 'severity.error': `REGEXP_CONTAINS(event_message, "level.{3}error|level.{3}fatal")`,
- 'severity.warning': `REGEXP_CONTAINS(event_message, "level.{3}warning")`,
- 'severity.info': `REGEXP_CONTAINS(event_message, "level.{3}info")`,
- 'status_code.server_error': `REGEXP_CONTAINS(event_message, "status.{3}5[0-9]{2}")`,
- 'status_code.client_error': `REGEXP_CONTAINS(event_message, "status.{3}4[0-9]{2}")`,
- 'status_code.redirection': `REGEXP_CONTAINS(event_message, "status.{3}3[0-9]{2}")`,
- 'status_code.success': `REGEXP_CONTAINS(event_message, "status.{3}2[0-9]{2}")`,
- 'endpoints.admin': `REGEXP_CONTAINS(event_message, "path.{3}/admin")`,
- 'endpoints.signup': `REGEXP_CONTAINS(event_message, "path.{3}/signup|path.{3}/invite|path.{3}/verify")`,
- 'endpoints.authentication': `REGEXP_CONTAINS(event_message, "path.{3}/token|path.{3}/authorize|path.{3}/callback|path.{3}/otp|path.{3}/magiclink")`,
- 'endpoints.recover': `REGEXP_CONTAINS(event_message, "path.{3}/recover")`,
- 'endpoints.user': `REGEXP_CONTAINS(event_message, "path.{3}/user")`,
- 'endpoints.logout': `REGEXP_CONTAINS(event_message, "path.{3}/logout")`,
+ 'severity.error': `metadata.level = 'error' or metadata.level = 'fatal'`,
+ 'severity.warning': `metadata.level = 'warning'`,
+ 'severity.info': `metadata.level = 'info'`,
+ 'status_code.server_error': `metadata.status between 500 and 599`,
+ 'status_code.client_error': `metadata.status between 400 and 499`,
+ 'status_code.redirection': `metadata.status between 300 and 399`,
+ 'status_code.success': `metadata.status between 200 and 299`,
+ 'endpoints.admin': `REGEXP_CONTAINS(metadata.path, "/admin")`,
+ 'endpoints.signup': `REGEXP_CONTAINS(metadata.path, "/signup|/invite|/verify")`,
+ 'endpoints.authentication': `REGEXP_CONTAINS(metadata.path, "/token|/authorize|/callback|/otp|/magiclink")`,
+ 'endpoints.recover': `REGEXP_CONTAINS(metadata.path, "/recover")`,
+ 'endpoints.user': `REGEXP_CONTAINS(metadata.path, "/user")`,
+ 'endpoints.logout': `REGEXP_CONTAINS(metadata.path, "/logout")`,
},
realtime_logs: {
..._SQL_FILTER_COMMON,
@@ -658,12 +658,12 @@ export const getDefaultHelper = (helpers: DatetimeHelper[]) =>
helpers.find((helper) => helper.default) || helpers[0]
export const TIER_QUERY_LIMITS: {
- [x: string]: { text: string; value: 1 | 7 | 90; unit: 'day'; promptUpgrade: boolean }
+ [x: string]: { text: string; value: 1 | 7 | 28 | 90; unit: 'day'; promptUpgrade: boolean }
} = {
FREE: { text: '1 day', value: 1, unit: 'day', promptUpgrade: true },
PRO: { text: '7 days', value: 7, unit: 'day', promptUpgrade: true },
- PAYG: { text: '90 days', value: 90, unit: 'day', promptUpgrade: false },
- TEAM: { text: '90 days', value: 90, unit: 'day', promptUpgrade: false },
+ PAYG: { text: '7 days', value: 7, unit: 'day', promptUpgrade: true },
+ TEAM: { text: '28 days', value: 28, unit: 'day', promptUpgrade: true },
ENTERPRISE: { text: '90 days', value: 90, unit: 'day', promptUpgrade: false },
}
diff --git a/studio/components/interfaces/Settings/Logs/Logs.utils.ts b/studio/components/interfaces/Settings/Logs/Logs.utils.ts
index 45d9dea510a..0006f609e88 100644
--- a/studio/components/interfaces/Settings/Logs/Logs.utils.ts
+++ b/studio/components/interfaces/Settings/Logs/Logs.utils.ts
@@ -150,6 +150,13 @@ export const genDefaultQuery = (table: LogsTableName, filters: Filters) => {
limit 100
`
+ case 'auth_logs':
+ return `select id, ${table}.timestamp, event_message, metadata.level, metadata.status, metadata.path, metadata.msg as msg, metadata.error from ${table}
+ cross join unnest(metadata) as metadata
+ ${where}
+ limit 100
+ `
+
case 'function_edge_logs':
return `select id, ${table}.timestamp, event_message, response.status_code, request.method, m.function_id, m.execution_time_ms, m.deployment_id, m.version from ${table}
cross join unnest(metadata) as m
@@ -239,6 +246,7 @@ export const genChartQuery = (
return `
SELECT
+-- event-chart
timestamp_trunc(t.timestamp, ${trunc}) as timestamp,
count(t.timestamp) as count
FROM
@@ -367,7 +375,7 @@ export const useEditorHints = () => {
export const fillTimeseries = (
timeseriesData: any[],
timestampKey: string,
- valueKey: string,
+ valueKey: string | string[],
defaultValue: number,
min?: string,
max?: string
@@ -390,10 +398,21 @@ export const fillTimeseries = (
const diff = maxDate.diff(minDate, truncation as dayjs.UnitType)
for (let i = 0; i <= diff; i++) {
const dateToMaybeAdd = minDate.add(i, truncation as dayjs.ManipulateType)
+
+ const keys = typeof valueKey === 'string' ? [valueKey] : valueKey
+
+ const toMerge = keys.reduce(
+ (acc, key) => ({
+ ...acc,
+ [key]: defaultValue,
+ }),
+ {}
+ )
+
if (!dates.find((d) => isEqual(d, dateToMaybeAdd))) {
newData.push({
[timestampKey]: dateToMaybeAdd.toISOString(),
- [valueKey]: defaultValue,
+ ...toMerge,
})
}
}
diff --git a/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx b/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx
index e089d38a65f..4b01e08c126 100644
--- a/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx
+++ b/studio/components/interfaces/Settings/Logs/LogsPreviewer.tsx
@@ -57,10 +57,20 @@ export const LogsPreviewer: React.FC
= ({
const table = !tableName ? LOGS_TABLES[queryType] : tableName
- const [
- { error, logData, params, newCount, filters, isLoading, eventChartData, isLoadingOlder },
- { loadOlder, setFilters, refresh, setParams },
- ] = useLogsPreview(projectRef as string, table, filterOverride)
+ const {
+ error,
+ logData,
+ params,
+ newCount,
+ filters,
+ isLoading,
+ eventChartData,
+ isLoadingOlder,
+ loadOlder,
+ setFilters,
+ refresh,
+ setParams,
+ } = useLogsPreview(projectRef as string, table, filterOverride)
const { showUpgradePrompt, setShowUpgradePrompt } = useUpgradePrompt(
params.iso_timestamp_start as string
@@ -231,7 +241,11 @@ export const LogsPreviewer: React.FC = ({
Load older
-
+
)}
diff --git a/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx b/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx
index 7258a5e9a2a..1a3b564ad34 100644
--- a/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx
+++ b/studio/components/interfaces/Settings/Logs/LogsQueryPanel.tsx
@@ -13,7 +13,7 @@ import DatePickers from './Logs.DatePickers'
import Link from 'next/link'
import React from 'react'
import { checkPermissions } from 'hooks'
-import { useProfileQuery } from 'data/profile/profile-query'
+import useProfile from 'hooks/misc/useProfile'
import { IS_PLATFORM } from 'lib/constants'
export interface LogsQueryPanelProps {
@@ -45,7 +45,7 @@ const LogsQueryPanel = ({
onDateChange,
warnings,
}: LogsQueryPanelProps) => {
- const { data: profile } = useProfileQuery()
+ const { data: profile } = useProfile()
const canCreateLogQuery = checkPermissions(PermissionAction.CREATE, 'user_content', {
resource: { type: 'log_sql', owner_id: profile?.id },
subject: { id: profile?.id },
diff --git a/studio/components/interfaces/Settings/Logs/UpgradePrompt.tsx b/studio/components/interfaces/Settings/Logs/UpgradePrompt.tsx
index 431cfb6afe9..a4c71cd7f19 100644
--- a/studio/components/interfaces/Settings/Logs/UpgradePrompt.tsx
+++ b/studio/components/interfaces/Settings/Logs/UpgradePrompt.tsx
@@ -4,17 +4,22 @@ import { TIER_QUERY_LIMITS } from '.'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useFlag } from 'hooks'
+import { StripeSubscription } from 'components/interfaces/Billing'
+import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants'
interface Props {
show: boolean
setShowUpgradePrompt: (value: boolean) => void
+ subscription: StripeSubscription | undefined
}
-const UpgradePrompt: React.FC
= ({ show, setShowUpgradePrompt }) => {
+const UpgradePrompt: React.FC = ({ show, setShowUpgradePrompt, subscription }) => {
const router = useRouter()
const { ref } = router.query
- const teamTierEnabled = useFlag('teamTier')
+ // Team tier is enabled when the flag is turned on OR the user is already on the team tier (manually assigned by us)
+ const userIsOnTeamTier = subscription?.tier?.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM
+ const teamTierEnabled = useFlag('teamTier') || userIsOnTeamTier
return (
= ({ setSentCategory }) => {
}
}, [uploadedFiles])
- const { data: profile } = useProfileQuery()
+ const { data: profile } = useProfile()
const respondToEmail = profile?.primary_email ?? 'your email'
if (!isInitialized) {
diff --git a/studio/components/layouts/AccountLayout/AccountLayout.tsx b/studio/components/layouts/AccountLayout/AccountLayout.tsx
index 29ddcca6914..af70d1570c7 100644
--- a/studio/components/layouts/AccountLayout/AccountLayout.tsx
+++ b/studio/components/layouts/AccountLayout/AccountLayout.tsx
@@ -3,12 +3,11 @@ import { FC, ReactNode } from 'react'
import { useRouter } from 'next/router'
import { observer } from 'mobx-react-lite'
-import { auth, STORAGE_KEY } from 'lib/gotrue'
+import { useSignOut } from 'lib/auth'
import { useStore, withAuth, useFlag } from 'hooks'
import { IS_PLATFORM } from 'lib/constants'
import WithSidebar from './WithSidebar'
import { SidebarSection } from './AccountLayout.types'
-import { useQueryClient } from '@tanstack/react-query'
interface Props {
title: string
@@ -22,16 +21,15 @@ interface Props {
const AccountLayout: FC = ({ children, title, breadcrumbs }) => {
const router = useRouter()
const { app, ui } = useStore()
- const queryClient = useQueryClient()
const ongoingIncident = useFlag('ongoingIncident')
const maxHeight = ongoingIncident ? 'calc(100vh - 44px)' : '100vh'
+ const signOut = useSignOut()
const onClickLogout = async () => {
- await auth.signOut()
- localStorage.removeItem(STORAGE_KEY)
+ await signOut()
+
await router.push('/sign-in')
- await queryClient.resetQueries()
}
const organizationsLinks = app.organizations
diff --git a/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx b/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx
index deb9fb8ab91..95dbd1011fa 100644
--- a/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx
+++ b/studio/components/layouts/DatabaseLayout/DatabaseLayout.tsx
@@ -26,6 +26,7 @@ const DatabaseLayout: FC = ({ title, children }) => {
const vaultExtension = meta.extensions.byId('supabase_vault')
const isVaultEnabled = vaultExtension !== undefined && vaultExtension.installed_version !== null
const foreignDataWrappersEnabled = useFlag('foreignDataWrappers')
+ const pgNetExtensionExists = meta.extensions.byId('pg_net') !== undefined
const [loaded, setLoaded] = useState(isInitialized)
@@ -73,7 +74,7 @@ const DatabaseLayout: FC = ({ title, children }) => {
isLoading={!loaded}
product="Database"
productMenu={
-
+
}
>
diff --git a/studio/components/layouts/DatabaseLayout/DatabaseMenu.utils.ts b/studio/components/layouts/DatabaseLayout/DatabaseMenu.utils.ts
index cb37f05173e..064d1e70c42 100644
--- a/studio/components/layouts/DatabaseLayout/DatabaseMenu.utils.ts
+++ b/studio/components/layouts/DatabaseLayout/DatabaseMenu.utils.ts
@@ -4,13 +4,11 @@ import { IS_PLATFORM } from 'lib/constants'
export const generateDatabaseMenu = (
project?: Project,
- foreignDataWrappersEnabled: boolean = false
+ foreignDataWrappersEnabled: boolean = false,
+ pgNetExtensionExists: boolean = false
): ProductMenuGroup[] => {
const ref = project?.ref ?? 'default'
- const HOOKS_RELEASED = '2021-07-30T15:33:54.383Z'
- const showHooksRoute = project?.inserted_at ? project.inserted_at > HOOKS_RELEASED : false
-
return [
{
title: 'Database',
@@ -41,7 +39,7 @@ export const generateDatabaseMenu = (
url: `/project/${ref}/database/replication`,
items: [],
},
- ...(showHooksRoute
+ ...(pgNetExtensionExists
? [
{
name: 'Webhooks',
diff --git a/studio/components/layouts/SQLEditorLayout/QueryItem.tsx b/studio/components/layouts/SQLEditorLayout/QueryItem.tsx
index 37393cd18c8..a655aff998a 100644
--- a/studio/components/layouts/SQLEditorLayout/QueryItem.tsx
+++ b/studio/components/layouts/SQLEditorLayout/QueryItem.tsx
@@ -1,4 +1,4 @@
-import { useState } from 'react'
+import { useState, useEffect, useRef } from 'react'
import { useParams } from 'common'
import ConfirmationModal from 'components/ui/ConfirmationModal'
import { useContentDeleteMutation } from 'data/content/content-delete-mutation'
@@ -21,14 +21,26 @@ const QueryItem = ({ tabInfo }: QueryItemProps) => {
const { ref, id: activeId } = useParams()
const { id, name } = tabInfo || {}
const isActive = id === activeId
+ const activeItemRef = useRef(null)
+
+ useEffect(() => {
+ // scroll to active item
+ if (isActive && activeItemRef.current) {
+ // race condition hack
+ setTimeout(() => {
+ activeItemRef.current?.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
+ }, 0)
+ }
+ })
return (
) : null}
>
@@ -127,14 +139,12 @@ const QueryItemActions = observer(({ tabInfo }: { tabInfo: SqlSnippet }) => {
) : (
)}
-
-
{
const { ui } = useStore()
const { ref, id } = useParams()
const router = useRouter()
- const { data: profile } = useProfileQuery()
+ const { data: profile } = useProfile()
const [filterString, setFilterString] = useState('')
const { setPages, setIsOpen } = useCommandMenu()
const showCmdkHelper = useFlag('dashboardCmdk')
@@ -61,7 +61,6 @@ const SideBarContent = observer(() => {
filterString.length === 0
? queries
: queries.filter((tab) => tab.name.toLowerCase().includes(filterString.toLowerCase()))
-
const canCreateSQLSnippet = checkPermissions(PermissionAction.CREATE, 'user_content', {
resource: { type: 'sql', owner_id: profile?.id },
subject: { id: profile?.id },
@@ -112,6 +111,8 @@ const SideBarContent = observer(() => {
{
{queryTabs.length >= 1 && (
-
+
{queryTabs.map((tabInfo) => {
const { id } = tabInfo || {}
return
diff --git a/studio/components/to-be-cleaned/Charts/ChartRenderer.tsx b/studio/components/to-be-cleaned/Charts/ChartRenderer.tsx
index 88812c5307e..3e0651f0825 100644
--- a/studio/components/to-be-cleaned/Charts/ChartRenderer.tsx
+++ b/studio/components/to-be-cleaned/Charts/ChartRenderer.tsx
@@ -214,7 +214,7 @@ export function BarChart({
interval={data ? data.length - 2 : 0}
angle={0}
// stroke="#4B5563"
- tick={{ fontSize: '0px', color: CHART_COLORS.TICK }}
+ tick={false}
axisLine={{ stroke: CHART_COLORS.AXIS }}
tickLine={{ stroke: CHART_COLORS.AXIS }}
/>
diff --git a/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx b/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx
index 0b4ae398b2e..2da515fa150 100644
--- a/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx
+++ b/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx
@@ -44,7 +44,7 @@ const StorageConfig = ({ config, projectRef }: any) => {
const [selectedUnit, setSelectedUnit] = useState(unit)
let initialValues = { fileSizeLimit: value, unformattedFileSizeLimit: fileSizeLimit }
- const canUpdateStorageSettings = checkPermissions(PermissionAction.UPDATE, 'projects')
+ const canUpdateStorageSettings = checkPermissions(PermissionAction.STORAGE_ADMIN_WRITE, '*')
const formattedMaxSizeBytes = `${new Intl.NumberFormat('en-US').format(
STORAGE_FILE_SIZE_LIMIT_MAX_BYTES
diff --git a/studio/components/ui/Charts/AreaChart.tsx b/studio/components/ui/Charts/AreaChart.tsx
index d7050f5926a..f83fcf91625 100644
--- a/studio/components/ui/Charts/AreaChart.tsx
+++ b/studio/components/ui/Charts/AreaChart.tsx
@@ -41,7 +41,7 @@ const AreaChart: React.FC
= ({
const { Container } = useChartSize(size)
const [focusDataIndex, setFocusDataIndex] = useState(null)
- if (data.length === 0) return
+ if (data.length === 0) return
const day = (value: number | string) => (displayDateInUtc ? dayjs(value).utc() : dayjs(value))
const resolvedHighlightedLabel =
@@ -97,7 +97,7 @@ const AreaChart: React.FC = ({
interval={data.length - 2}
angle={0}
// hide the tick
- tick={{ fontSize: '0px' }}
+ tick={false}
// color the axis
axisLine={{ stroke: CHART_COLORS.AXIS }}
tickLine={{ stroke: CHART_COLORS.AXIS }}
diff --git a/studio/components/ui/Charts/BarChart.tsx b/studio/components/ui/Charts/BarChart.tsx
index 8d371a1c89a..2da9cf9a76e 100644
--- a/studio/components/ui/Charts/BarChart.tsx
+++ b/studio/components/ui/Charts/BarChart.tsx
@@ -37,7 +37,7 @@ const BarChart: React.FC = ({
const { Container } = useChartSize(size)
const [focusDataIndex, setFocusDataIndex] = useState(null)
- if (data.length === 0) return
+ if (data.length === 0) return
const day = (value: number | string) => (displayDateInUtc ? dayjs(value).utc() : dayjs(value))
const resolvedHighlightedLabel =
@@ -48,7 +48,7 @@ const BarChart: React.FC = ({
highlightedLabel
const resolvedHighlightedValue =
- (focusDataIndex !== null ? data[focusDataIndex]?.[yAxisKey] : highlightedValue)
+ focusDataIndex !== null ? data[focusDataIndex]?.[yAxisKey] : highlightedValue
return (
@@ -92,7 +92,7 @@ const BarChart: React.FC
= ({
interval={data.length - 2}
angle={0}
// hide the tick
- tick={{ fontSize: '0px' }}
+ tick={false}
// color the axis
axisLine={{ stroke: CHART_COLORS.AXIS }}
tickLine={{ stroke: CHART_COLORS.AXIS }}
diff --git a/studio/components/ui/Charts/NoDataPlaceholder.tsx b/studio/components/ui/Charts/NoDataPlaceholder.tsx
index 6a7a7e29c72..54f36619dfe 100644
--- a/studio/components/ui/Charts/NoDataPlaceholder.tsx
+++ b/studio/components/ui/Charts/NoDataPlaceholder.tsx
@@ -1,27 +1,34 @@
import { IconBarChart2 } from 'ui'
+import { useChartSize } from './Charts.utils'
interface Props {
title?: string
message?: string
className?: string
+ size: Parameters[0]
}
const NoDataPlaceholder: React.FC = ({
title = 'No data to show',
message,
className = '',
-}) => (
-
-
-
-
{title}
- {message &&
{message}
}
+ size,
+}) => {
+ const { minHeight } = useChartSize(size)
+ return (
+
+
+
+
{title}
+ {message &&
{message}
}
+
-
-)
+ )
+}
export default NoDataPlaceholder
diff --git a/studio/components/ui/Charts/StackedBarChart.tsx b/studio/components/ui/Charts/StackedBarChart.tsx
index d378cef19a8..aceea8560d3 100644
--- a/studio/components/ui/Charts/StackedBarChart.tsx
+++ b/studio/components/ui/Charts/StackedBarChart.tsx
@@ -43,7 +43,7 @@ const StackedBarChart: React.FC
= ({
variant,
})
const [focusDataIndex, setFocusDataIndex] = useState(null)
- if (!data || data.length === 0) return
+ if (!data || data.length === 0) return
return (
{!hideHeader && (
@@ -84,7 +84,7 @@ const StackedBarChart: React.FC
= ({
dataKey={xAxisKey}
interval={data.length - 2}
angle={0}
- tick={{ fontSize: '0px' }}
+ tick={false}
axisLine={{ stroke: CHART_COLORS.AXIS }}
tickLine={{ stroke: CHART_COLORS.AXIS }}
/>
diff --git a/studio/components/ui/PageTelemetry.tsx b/studio/components/ui/PageTelemetry.tsx
index f6de24e69b6..6d9d469ef9a 100644
--- a/studio/components/ui/PageTelemetry.tsx
+++ b/studio/components/ui/PageTelemetry.tsx
@@ -5,24 +5,6 @@ import { observer } from 'mobx-react-lite'
import { useRouter } from 'next/router'
import { FC, useEffect } from 'react'
-function sanitizePageViewRoute(_route?: string) {
- // remove all fragments
- const noFragments = _route?.split('#')[0]
- // remove sensitive params
- const paramsSplits = noFragments?.split('?')
- const hasParams = paramsSplits && paramsSplits?.length > 1
-
- if (hasParams) {
- const urlParams = new URLSearchParams(paramsSplits[1])
- const sensitiveKeys = [...urlParams.keys()].filter((x) => x.includes('token'))
- const sensitiveParams = ['code', ...sensitiveKeys]
- sensitiveParams.forEach((name) => urlParams.delete(name))
- return `${paramsSplits[0]}?${urlParams?.toString()}`
- }
-
- return noFragments
-}
-
const PageTelemetry: FC = ({ children }) => {
const router = useRouter()
const telemetryProps = useTelemetryProps()
@@ -53,11 +35,8 @@ const PageTelemetry: FC = ({ children }) => {
*
* @param route: the browser url
* */
- const handlePageTelemetry = async (_route?: string) => {
+ const handlePageTelemetry = async (route: string) => {
if (IS_PLATFORM) {
- // filter out sensitive query params
- const route = sanitizePageViewRoute(_route)
-
/**
* Get referrer from browser
*/
diff --git a/studio/data/analytics/keys.ts b/studio/data/analytics/keys.ts
index 049d7d0a0a0..ad9e94223b1 100644
--- a/studio/data/analytics/keys.ts
+++ b/studio/data/analytics/keys.ts
@@ -30,4 +30,7 @@ export const analyticsKeys = {
'infra-monitoring',
{ attribute, startDate, endDate, interval },
] as const,
+ usageApiCounts: (projectRef: string | undefined, interval: string | undefined) =>
+ ['projects', projectRef, 'usage.api-counts', interval] as const,
}
+
diff --git a/studio/data/logs/project-log-stats-query.ts b/studio/data/analytics/project-log-stats-query.ts
similarity index 72%
rename from studio/data/logs/project-log-stats-query.ts
rename to studio/data/analytics/project-log-stats-query.ts
index b14039751e9..023b1c0cfdb 100644
--- a/studio/data/logs/project-log-stats-query.ts
+++ b/studio/data/analytics/project-log-stats-query.ts
@@ -2,14 +2,23 @@ import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query
import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { useCallback } from 'react'
-import { logKeys } from './keys'
+import { analyticsKeys } from './keys'
export type ProjectLogStatsVariables = {
projectRef?: string
interval?: string
}
-export type ProjectLogStatsResponse = any
+export type ProjectLogStatsResponse = {
+ result: UsageApiCounts[]
+}
+export interface UsageApiCounts {
+ total_auth_requests: number
+ total_storage_requests: number
+ total_rest_requests: number
+ total_realtime_requests: number
+ timestamp: string
+}
export async function getProjectLogStats(
{ projectRef, interval }: ProjectLogStatsVariables,
@@ -22,9 +31,12 @@ export async function getProjectLogStats(
throw new Error('interval is required')
}
- const response = await get(`${API_URL}/projects/${projectRef}/log-stats?interval=${interval}`, {
- signal,
- })
+ const response = await get(
+ `${API_URL}/projects/${projectRef}/analytics/endpoints/usage.api-counts?interval=${interval}`,
+ {
+ signal,
+ }
+ )
if (response.error) {
throw response.error
}
@@ -43,7 +55,7 @@ export const useProjectLogStatsQuery = (
}: UseQueryOptions = {}
) =>
useQuery(
- logKeys.logStats(projectRef, interval),
+ analyticsKeys.usageApiCounts(projectRef, interval),
({ signal }) => getProjectLogStats({ projectRef, interval }, signal),
{
enabled: enabled && typeof projectRef !== 'undefined' && typeof interval !== 'undefined',
@@ -56,7 +68,7 @@ export const useProjectLogStatsPrefetch = ({ projectRef, interval }: ProjectLogS
return useCallback(() => {
if (projectRef && interval) {
- client.prefetchQuery(logKeys.logStats(projectRef, interval), ({ signal }) =>
+ client.prefetchQuery(analyticsKeys.usageApiCounts(projectRef, interval), ({ signal }) =>
getProjectLogStats({ projectRef, interval }, signal)
)
}
diff --git a/studio/data/api-authorization/api-authorization-query.ts b/studio/data/api-authorization/api-authorization-query.ts
index 652c19c4d5f..837738e0045 100644
--- a/studio/data/api-authorization/api-authorization-query.ts
+++ b/studio/data/api-authorization/api-authorization-query.ts
@@ -11,6 +11,7 @@ export type ApiAuthorizationVariables = {
export type ApiAuthorizationResponse = {
name: string
website: string
+ icon: string | null
domain: string
expires_at: string
approved_at: string | null
diff --git a/studio/data/logs/keys.ts b/studio/data/logs/keys.ts
deleted file mode 100644
index c9ba47f22aa..00000000000
--- a/studio/data/logs/keys.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export const logKeys = {
- logStats: (projectRef: string | undefined, interval: string | undefined) =>
- ['projects', projectRef, 'log-stats', interval] as const,
-}
diff --git a/studio/data/profile/profile-create-mutation.ts b/studio/data/profile/profile-create-mutation.ts
new file mode 100644
index 00000000000..01f1ffff75c
--- /dev/null
+++ b/studio/data/profile/profile-create-mutation.ts
@@ -0,0 +1,34 @@
+import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
+import { post } from 'lib/common/fetch'
+import { API_URL } from 'lib/constants'
+import { profileKeys } from './keys'
+import { Profile } from './types'
+
+export type ProfileResponse = Profile
+
+export async function createProfile() {
+ const response = await post(`${API_URL}/profile`, {})
+ if (response.error) {
+ throw response.error
+ }
+
+ return response as ProfileResponse
+}
+
+type ProfileCreateData = Awaited>
+
+export const useProfileCreateMutation = ({
+ onSuccess,
+ ...options
+}: Omit, 'mutationFn'> = {}) => {
+ const queryClient = useQueryClient()
+
+ return useMutation(() => createProfile(), {
+ async onSuccess(data, variables, context) {
+ await queryClient.invalidateQueries(profileKeys.profile())
+
+ await onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
diff --git a/studio/data/profile/profile-query.ts b/studio/data/profile/profile-query.ts
index 479cb7233aa..a6e990485a4 100644
--- a/studio/data/profile/profile-query.ts
+++ b/studio/data/profile/profile-query.ts
@@ -3,25 +3,16 @@ import { get } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { useCallback } from 'react'
import { profileKeys } from './keys'
-
-export type Profile = {
- id: number
- auth0_id: string
- primary_email: string
- username: string
- first_name: string
- last_name: string
- mobile: string | null
- is_alpha_user: boolean
- gotrue_id: string
- free_project_limit: number
-}
+import { Profile } from './types'
export type ProfileResponse = Profile
export async function getProfile(signal?: AbortSignal) {
const response = await get(`${API_URL}/profile`, {
signal,
+ headers: {
+ Version: '2',
+ },
})
if (response.error) {
throw response.error
@@ -36,8 +27,8 @@ export type ProfileError = unknown
export const useProfileQuery = ({
enabled = true,
...options
-}: UseQueryOptions = {}) =>
- useQuery(
+}: UseQueryOptions = {}) => {
+ return useQuery(
profileKeys.profile(),
({ signal }) => getProfile(signal),
{
@@ -45,6 +36,7 @@ export const useProfileQuery = ({
...options,
}
)
+}
export const useProfilePrefetch = () => {
const client = useQueryClient()
diff --git a/studio/data/profile/profile-update-mutation.ts b/studio/data/profile/profile-update-mutation.ts
index b1b9ba67252..d4633f184bb 100644
--- a/studio/data/profile/profile-update-mutation.ts
+++ b/studio/data/profile/profile-update-mutation.ts
@@ -1,5 +1,5 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
-import { post } from 'lib/common/fetch'
+import { patch } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { profileKeys } from './keys'
@@ -9,7 +9,7 @@ export type ProfileUpdateVariables = {
}
export async function updateProfile({ firstName, lastName }: ProfileUpdateVariables) {
- const response = await post(`${API_URL}/profile/update`, {
+ const response = await patch(`${API_URL}/profile`, {
first_name: firstName,
last_name: lastName,
})
diff --git a/studio/data/profile/types.ts b/studio/data/profile/types.ts
new file mode 100644
index 00000000000..4af9a4d09e9
--- /dev/null
+++ b/studio/data/profile/types.ts
@@ -0,0 +1,12 @@
+export type Profile = {
+ id: number
+ auth0_id: string
+ primary_email: string
+ username: string
+ first_name: string
+ last_name: string
+ mobile: string | null
+ is_alpha_user: boolean
+ gotrue_id: string
+ free_project_limit: number
+}
diff --git a/studio/data/query-client.ts b/studio/data/query-client.ts
index ee9ce6f2522..73cbb94e64b 100644
--- a/studio/data/query-client.ts
+++ b/studio/data/query-client.ts
@@ -4,7 +4,31 @@ import { useState } from 'react'
let queryClient: QueryClient | undefined
export function getQueryClient() {
- const _queryClient = queryClient ?? new QueryClient()
+ const _queryClient =
+ queryClient ??
+ new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: (failureCount, error) => {
+ // Don't retry on 404s
+ if (
+ typeof error === 'object' &&
+ error !== null &&
+ 'code' in error &&
+ (error as any).code === 404
+ ) {
+ return false
+ }
+
+ if (failureCount < 3) {
+ return true
+ }
+
+ return false
+ },
+ },
+ },
+ })
// For SSG and SSR always create a new queryClient
if (typeof window === 'undefined') return _queryClient
diff --git a/studio/data/table-rows/utils.ts b/studio/data/table-rows/utils.ts
index b12295252d7..6ae498e3404 100644
--- a/studio/data/table-rows/utils.ts
+++ b/studio/data/table-rows/utils.ts
@@ -10,7 +10,8 @@ export function formatFilterValue(table: SupaTable, filter: Filter) {
const column = table.columns.find((x) => x.name == filter.column)
if (column && isNumericalColumn(column.format)) {
const numberValue = Number(filter.value)
- if (Number.isNaN(numberValue)) return filter.value
+ // Supports BigInt filter values
+ if (Number.isNaN(numberValue) || numberValue > Number.MAX_SAFE_INTEGER) return filter.value
else return Number(filter.value)
}
return filter.value
diff --git a/studio/hooks/analytics/useDbQuery.tsx b/studio/hooks/analytics/useDbQuery.tsx
index 1fab5b86eea..38a0e45a9dd 100644
--- a/studio/hooks/analytics/useDbQuery.tsx
+++ b/studio/hooks/analytics/useDbQuery.tsx
@@ -2,19 +2,27 @@ import { useQuery } from '@tanstack/react-query'
import { DEFAULT_QUERY_PARAMS } from 'components/interfaces/Reports/Reports.constants'
import {
BaseReportParams,
- DbQueryData,
- DbQueryHandler,
MetaQueryResponse,
ReportQuery,
} from 'components/interfaces/Reports/Reports.types'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { executeSql } from 'data/sql/execute-sql-query'
-type UseDbQuery = (
+export interface DbQueryHook {
+ isLoading: boolean
+ error: string
+ data: T[]
+ params: BaseReportParams
+ logData?: never
+ runQuery: () => void
+ setParams?: never
+ changeQuery?: never
+}
+
+const useDbQuery = (
sql: ReportQuery['sql'],
- params?: BaseReportParams
-) => [DbQueryData, DbQueryHandler]
-const useDbQuery: UseDbQuery = (sql, params = DEFAULT_QUERY_PARAMS) => {
+ params: BaseReportParams = DEFAULT_QUERY_PARAMS
+): DbQueryHook => {
const { project } = useProjectContext()
const resolvedSql = typeof sql === 'function' ? sql([]) : sql
@@ -45,10 +53,7 @@ const useDbQuery: UseDbQuery = (sql, params = DEFAULT_QUERY_PARAMS) => {
)
const error = rqError || (typeof data === 'object' ? data?.error : '')
- return [
- { error, data, isLoading: isLoading || isRefetching, params },
- { runQuery: () => refetch() },
- ]
+ return { error, data, isLoading: isLoading || isRefetching, params, runQuery: refetch }
}
export default useDbQuery
diff --git a/studio/hooks/analytics/useLogsPreview.tsx b/studio/hooks/analytics/useLogsPreview.tsx
index 3e4a55589bc..eb1570d9c39 100644
--- a/studio/hooks/analytics/useLogsPreview.tsx
+++ b/studio/hooks/analytics/useLogsPreview.tsx
@@ -15,14 +15,14 @@ import {
LogsTableName,
PREVIEWER_DATEPICKER_HELPERS,
} from 'components/interfaces/Settings/Logs'
-import { Dispatch, SetStateAction, useEffect, useMemo, useRef, useState } from 'react'
+import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react'
import { API_URL } from 'lib/constants'
import { get } from 'lib/common/fetch'
import dayjs from 'dayjs'
import useFillTimeseriesSorted from './useFillTimeseriesSorted'
import useTimeseriesUnixToIso from './useTimeseriesUnixToIso'
-interface Data {
+interface LogsPreviewHook {
logData: LogData[]
error: string | Object | null
newCount: number
@@ -32,8 +32,6 @@ interface Data {
params: LogsEndpointParams
oldestTimestamp?: string
eventChartData: EventChartData[]
-}
-interface Handlers {
loadOlder: () => void
refresh: () => void
setFilters: (filters: Filters | ((previous: Filters) => Filters)) => void
@@ -43,7 +41,7 @@ function useLogsPreview(
projectRef: string,
table: LogsTableName,
filterOverride?: Filters
-): [Data, Handlers] {
+): LogsPreviewHook {
const defaultHelper = getDefaultHelper(PREVIEWER_DATEPICKER_HELPERS)
const [latestRefresh, setLatestRefresh] = useState(new Date().toISOString())
@@ -168,7 +166,7 @@ function useLogsPreview(
const oldestTimestamp = logData[logData.length - 1]?.timestamp
- const handleSetFilters: Handlers['setFilters'] = (newFilters) => {
+ const handleSetFilters: LogsPreviewHook['setFilters'] = (newFilters) => {
if (typeof newFilters === 'function') {
setFilters((prev) => {
const resolved = newFilters(prev)
@@ -189,29 +187,25 @@ function useLogsPreview(
'timestamp',
'count',
0,
- params.iso_timestamp_start ,
+ params.iso_timestamp_start,
// default to current time if not set
params.iso_timestamp_end || new Date().toISOString()
)
- return [
- {
- newCount,
- logData,
- isLoading: isLoading || isRefetching,
- isLoadingOlder: isFetchingNextPage,
- error,
- filters,
- params,
- oldestTimestamp: oldestTimestamp ? String(oldestTimestamp) : undefined,
- eventChartData,
- },
- {
- setFilters: handleSetFilters,
- refresh,
- loadOlder: () => fetchNextPage(),
- setParams,
- },
- ]
+ return {
+ newCount,
+ logData,
+ isLoading: isLoading || isRefetching,
+ isLoadingOlder: isFetchingNextPage,
+ error,
+ filters,
+ params,
+ oldestTimestamp: oldestTimestamp ? String(oldestTimestamp) : undefined,
+ eventChartData,
+ setFilters: handleSetFilters,
+ refresh,
+ loadOlder: () => fetchNextPage(),
+ setParams,
+ }
}
export default useLogsPreview
diff --git a/studio/hooks/analytics/useLogsQuery.tsx b/studio/hooks/analytics/useLogsQuery.tsx
index 99fc2c41713..22553558efd 100644
--- a/studio/hooks/analytics/useLogsQuery.tsx
+++ b/studio/hooks/analytics/useLogsQuery.tsx
@@ -8,14 +8,12 @@ import { Dispatch, SetStateAction, useState } from 'react'
import { LogsEndpointParams, Logs, LogData } from 'components/interfaces/Settings/Logs/Logs.types'
import { API_URL } from 'lib/constants'
import { get } from 'lib/common/fetch'
-export interface LogsQueryData {
+export interface LogsQueryHook {
params: LogsEndpointParams
isLoading: boolean
logData: LogData[]
data?: never
error: string | Object | null
-}
-export interface LogsQueryHandlers {
changeQuery: (newQuery?: string) => void
runQuery: () => void
setParams: Dispatch>
@@ -24,7 +22,7 @@ export interface LogsQueryHandlers {
const useLogsQuery = (
projectRef: string,
initialParams: Partial = {}
-): [LogsQueryData, LogsQueryHandlers] => {
+): LogsQueryHook => {
const defaultHelper = getDefaultHelper(EXPLORER_DATEPICKER_HELPERS)
const [params, setParams] = useState({
sql: initialParams?.sql || '',
@@ -67,14 +65,14 @@ const useLogsQuery = (
setParams((prev) => ({ ...prev, sql: newQuery }))
}
- return [
- {
- params,
- isLoading: (enabled && isLoading) || isRefetching,
- logData: data?.result ? data?.result : [],
- error,
- },
- { changeQuery, runQuery: () => refetch(), setParams },
- ]
+ return {
+ params,
+ isLoading: (enabled && isLoading) || isRefetching,
+ logData: data?.result ? data?.result : [],
+ error,
+ changeQuery,
+ runQuery: () => refetch(),
+ setParams,
+ }
}
export default useLogsQuery
diff --git a/studio/hooks/analytics/useSingleLog.tsx b/studio/hooks/analytics/useSingleLog.tsx
index 690c5df5eb3..996ba91f756 100644
--- a/studio/hooks/analytics/useSingleLog.tsx
+++ b/studio/hooks/analytics/useSingleLog.tsx
@@ -11,12 +11,10 @@ import { API_URL } from 'lib/constants'
import { get } from 'lib/common/fetch'
import { useQuery } from '@tanstack/react-query'
-interface Data {
+interface SingleLogHook {
logData: LogData | undefined
error: string | Object | null
isLoading: boolean
-}
-interface Handlers {
refresh: () => void
}
function useSingleLog(
@@ -24,7 +22,7 @@ function useSingleLog(
queryType?: QueryType,
paramsToMerge?: Partial,
id?: string | null
-): [Data, Handlers] {
+): SingleLogHook {
const table = queryType ? LOGS_TABLES[queryType] : undefined
const sql = id && table ? genSingleLogQuery(table, id) : ''
const params: LogsEndpointParams = { ...paramsToMerge, project: projectRef, sql }
@@ -52,15 +50,11 @@ function useSingleLog(
)
let error: null | string | object = rcError ? (rcError as any).message : null
- return [
- {
- logData: data?.result ? data.result[0] : undefined,
- isLoading: (enabled && isLoading) || isRefetching,
- error,
- },
- {
- refresh: () => refetch(),
- },
- ]
+ return {
+ logData: data?.result ? data.result[0] : undefined,
+ isLoading: (enabled && isLoading) || isRefetching,
+ error,
+ refresh: () => refetch(),
+ }
}
export default useSingleLog
diff --git a/studio/hooks/misc/useProfile.tsx b/studio/hooks/misc/useProfile.tsx
new file mode 100644
index 00000000000..a41439114b3
--- /dev/null
+++ b/studio/hooks/misc/useProfile.tsx
@@ -0,0 +1,41 @@
+import { useRouter } from 'next/router'
+
+import { useTelemetryProps } from 'common'
+import { useProfileCreateMutation } from 'data/profile/profile-create-mutation'
+import { useProfileQuery } from 'data/profile/profile-query'
+import Telemetry from 'lib/telemetry'
+
+/**
+ * Fetches the user's profile, creating one if it does not exist
+ */
+function useProfile() {
+ const router = useRouter()
+ const telemetryProps = useTelemetryProps()
+
+ const { mutate: createProfile, isLoading: isCreating } = useProfileCreateMutation({
+ onSuccess() {
+ Telemetry.sendEvent(
+ { category: 'conversion', action: 'sign_up', label: '' },
+ telemetryProps,
+ router
+ )
+ },
+ })
+
+ const { isLoading, ...result } = useProfileQuery({
+ onError(err) {
+ // if the user does not yet exist, create a profile for them
+ if (typeof err === 'object' && err !== null && 'code' in err && (err as any).code === 404) {
+ createProfile()
+ }
+ },
+ })
+
+ return {
+ // Continue the loading state until the profile is created
+ isLoading: isLoading || isCreating,
+ ...result,
+ }
+}
+
+export default useProfile
diff --git a/studio/jest.config.js b/studio/jest.config.js
index 03b07573f70..1fccb491f93 100644
--- a/studio/jest.config.js
+++ b/studio/jest.config.js
@@ -3,7 +3,7 @@ module.exports = {
moduleDirectories: ['', 'node_modules'],
setupFiles: ['jest-canvas-mock', './tests/setup/radix'],
testEnvironment: 'jsdom',
- testTimeout: 20000,
+ testTimeout: 10000,
testRegex: '(.*\\.test.(js|jsx|ts|tsx)$)',
transform: { '^.+\\.(t|j)sx?$': 'ts-jest' },
globals: {
@@ -11,4 +11,6 @@ module.exports = {
tsconfig: 'tsconfig.test.json',
},
},
+ maxConcurrency: 3,
+ maxWorkers: '50%',
}
diff --git a/studio/lib/auth.tsx b/studio/lib/auth.tsx
index 55b17f74866..b42ac9c84bc 100644
--- a/studio/lib/auth.tsx
+++ b/studio/lib/auth.tsx
@@ -1,3 +1,7 @@
+import { useQueryClient } from '@tanstack/react-query'
+import { useRouter } from 'next/router'
+import { PropsWithChildren, useCallback, useEffect } from 'react'
+
import {
AuthContext as AuthContextInternal,
AuthProvider as AuthProviderInternal,
@@ -6,14 +10,15 @@ import {
} from 'common'
import { useProfileQuery } from 'data/profile/profile-query'
import { useStore } from 'hooks'
-import { PropsWithChildren, useEffect } from 'react'
-import { GOTRUE_ERRORS, IS_PLATFORM } from './constants'
import Telemetry from 'lib/telemetry'
+import { GOTRUE_ERRORS, IS_PLATFORM } from './constants'
+import { clearLocalStorage } from './local-storage'
export const AuthContext = AuthContextInternal
export const AuthProvider = ({ children }: PropsWithChildren<{}>) => {
const { ui, app } = useStore()
+ const router = useRouter()
const telemetryProps = useTelemetryProps()
// Check for unverified GitHub users after a GitHub sign in
@@ -33,6 +38,22 @@ export const AuthProvider = ({ children }: PropsWithChildren<{}>) => {
handleEmailVerificationError()
}, [])
+ useEffect(() => {
+ const {
+ data: { subscription },
+ } = gotrueClient.onAuthStateChange((event) => {
+ if (event === 'SIGNED_IN') {
+ Telemetry.sendEvent(
+ { category: 'account', action: 'sign_in', label: '' },
+ telemetryProps,
+ router
+ )
+ }
+ })
+
+ return subscription.unsubscribe
+ }, [])
+
// Track telemetry for the current user
useProfileQuery({
onSuccess(profile) {
@@ -53,3 +74,15 @@ export const AuthProvider = ({ children }: PropsWithChildren<{}>) => {
}
export { useAuth, useIsLoggedIn, useSession, useUser } from 'common'
+
+export function useSignOut() {
+ const queryClient = useQueryClient()
+
+ return useCallback(async () => {
+ const result = await gotrueClient.signOut()
+ clearLocalStorage()
+ await queryClient.resetQueries()
+
+ return result
+ }, [])
+}
diff --git a/studio/lib/local-storage.ts b/studio/lib/local-storage.ts
new file mode 100644
index 00000000000..611bcd1616c
--- /dev/null
+++ b/studio/lib/local-storage.ts
@@ -0,0 +1,9 @@
+export const LOCAL_STORAGE_KEYS_ALLOWLIST = ['graphiql:theme', 'theme', 'supabaseDarkMode']
+
+export function clearLocalStorage() {
+ for (const key in localStorage) {
+ if (!LOCAL_STORAGE_KEYS_ALLOWLIST.includes(key)) {
+ localStorage.removeItem(key)
+ }
+ }
+}
diff --git a/studio/pages/500.tsx b/studio/pages/500.tsx
index b49bc05df1e..e6f43a48713 100644
--- a/studio/pages/500.tsx
+++ b/studio/pages/500.tsx
@@ -5,16 +5,16 @@ import Image from 'next/image'
import { Button } from 'ui'
import { observer } from 'mobx-react-lite'
-import { auth, STORAGE_KEY } from 'lib/gotrue'
+import { useSignOut } from 'lib/auth'
import { useTheme } from 'common'
const Error500: NextPage = () => {
const router = useRouter()
const { isDarkMode } = useTheme()
+ const signOut = useSignOut()
const onClickLogout = async () => {
- await auth.signOut()
- localStorage.removeItem(STORAGE_KEY)
+ await signOut()
await router.push('/sign-in')
router.reload()
}
diff --git a/studio/pages/account/me.tsx b/studio/pages/account/me.tsx
index bc98dd9c7c9..cbe90639743 100644
--- a/studio/pages/account/me.tsx
+++ b/studio/pages/account/me.tsx
@@ -4,9 +4,10 @@ import { Button, IconMoon, IconSun, Input, Listbox } from 'ui'
import { AccountLayout } from 'components/layouts'
import SchemaFormPanel from 'components/to-be-cleaned/forms/SchemaFormPanel'
import Panel from 'components/ui/Panel'
-import { Profile as ProfileType, useProfileQuery } from 'data/profile/profile-query'
+import { Profile as ProfileType } from 'data/profile/types'
import { useProfileUpdateMutation } from 'data/profile/profile-update-mutation'
import { useStore } from 'hooks'
+import useProfile from 'hooks/misc/useProfile'
import { useSession } from 'lib/auth'
import Link from 'next/link'
import { NextPageWithLayout } from 'types'
@@ -40,7 +41,7 @@ const ProfileCard = observer(() => {
const { ui } = useStore()
const { mutateAsync } = useProfileUpdateMutation()
- const { data: profile } = useProfileQuery()
+ const { data: profile } = useProfile()
// TODO: ^ handle loading state
const updateUser = async (model: any) => {
diff --git a/studio/pages/api/storage/[ref]/buckets/[id]/objects/public-url.ts b/studio/pages/api/storage/[ref]/buckets/[id]/objects/public-url.ts
index 41efb4e8107..40a23910b6a 100644
--- a/studio/pages/api/storage/[ref]/buckets/[id]/objects/public-url.ts
+++ b/studio/pages/api/storage/[ref]/buckets/[id]/objects/public-url.ts
@@ -24,5 +24,12 @@ const handlePost = async (req: NextApiRequest, res: NextApiResponse) => {
const { data } = supabase.storage.from(id as string).getPublicUrl(path)
+ // change the domain name to the SUPABASE_PUBLIC_URL since SUPABASE_URL is not accessible from the client
+ const publicUrl = new URL(data.publicUrl)
+ const parsed = new URL(process.env.SUPABASE_PUBLIC_URL!)
+ publicUrl.protocol = parsed.protocol
+ publicUrl.host = parsed.host
+ data.publicUrl = publicUrl.href
+
return res.status(200).json(data)
}
diff --git a/studio/pages/api/storage/[ref]/buckets/[id]/objects/sign.ts b/studio/pages/api/storage/[ref]/buckets/[id]/objects/sign.ts
index c89791ab5f3..f976c44b822 100644
--- a/studio/pages/api/storage/[ref]/buckets/[id]/objects/sign.ts
+++ b/studio/pages/api/storage/[ref]/buckets/[id]/objects/sign.ts
@@ -29,7 +29,9 @@ const handlePost = async (req: NextApiRequest, res: NextApiResponse) => {
// change the domain name to the SUPABASE_PUBLIC_URL since SUPABASE_URL is not accessible from the client
const signedUrl = new URL(data.signedUrl)
- signedUrl.host = new URL(process.env.SUPABASE_PUBLIC_URL!).host
+ const parsed = new URL(process.env.SUPABASE_PUBLIC_URL!)
+ signedUrl.protocol = parsed.protocol
+ signedUrl.host = parsed.host
data.signedUrl = signedUrl.href
return res.status(200).json(data)
diff --git a/studio/pages/authorize.tsx b/studio/pages/authorize.tsx
index 86559bebe6e..a7f1c81e920 100644
--- a/studio/pages/authorize.tsx
+++ b/studio/pages/authorize.tsx
@@ -176,10 +176,13 @@ const APIAuthorizationPage: NextPageWithLayout = () => {
-
{requester?.name[0]}
+ {requester?.icon === null && (
+
{requester?.name[0]}
+ )}
diff --git a/studio/pages/join/index.tsx b/studio/pages/join/index.tsx
index 6651b06fcaf..ce5174adbce 100644
--- a/studio/pages/join/index.tsx
+++ b/studio/pages/join/index.tsx
@@ -4,10 +4,10 @@ import { useRouter } from 'next/router'
import { Button, IconCheckSquare, Loading } from 'ui'
import { useStore } from 'hooks'
-import { auth } from 'lib/gotrue'
+import useProfile from 'hooks/misc/useProfile'
+import { useSignOut } from 'lib/auth'
import { API_URL } from 'lib/constants'
import { get, post, delete_ } from 'lib/common/fetch'
-import { useProfileQuery } from 'data/profile/profile-query'
interface ITokenInfo {
organization_name?: string | undefined
@@ -24,7 +24,8 @@ const JoinOrganizationPage = () => {
const router = useRouter()
const { slug, token, name } = router.query
const { ui, app } = useStore()
- const { data: profile } = useProfileQuery()
+ const { data: profile } = useProfile()
+ const signOut = useSignOut()
const [isSubmitting, setIsSubmitting] = useState(false)
const [error, setError] = useState(false)
@@ -135,7 +136,7 @@ const JoinOrganizationPage = () => {
{
- await auth.signOut()
+ await signOut()
router.reload()
}}
>
diff --git a/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx b/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx
index 7e03f65d572..2e677cc2f81 100644
--- a/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx
+++ b/studio/pages/project/[ref]/functions/[functionSlug]/index.tsx
@@ -17,15 +17,17 @@ import AreaChart from 'components/ui/Charts/AreaChart'
import { isUnixMicro, unixMicroToIsoTimestamp } from 'components/interfaces/Settings/Logs'
import meanBy from 'lodash/meanBy'
import sumBy from 'lodash/sumBy'
+import useFillTimeseriesSorted from 'hooks/analytics/useFillTimeseriesSorted'
const CHART_INTERVALS: ChartIntervals[] = [
{
key: '5min',
label: '5 min',
- startValue: 1,
- startUnit: 'hour',
+ startValue: 5,
+ startUnit: 'minute',
+ format: 'MMM D, h:mm:ssa',
},
- { key: '15min', label: '15 min', startValue: 15, startUnit: 'min' },
+ { key: '15min', label: '15 min', startValue: 15, startUnit: 'minute' },
{ key: '1hr', label: '1 hour', startValue: 1, startUnit: 'hour' },
{ key: '1day', label: '1 day', startValue: 1, startUnit: 'day' },
{ key: '7day', label: '7 days', startValue: 7, startUnit: 'day' },
@@ -45,7 +47,7 @@ const PageLayout: NextPageWithLayout = () => {
interval: selectedInterval.key,
})
const isChartLoading = !data?.result && !error ? true : false
- const chartData = useMemo(() => {
+ const normalizedData = useMemo(() => {
return (data?.result || []).map((d: any) => ({
...d,
timestamp: isUnixMicro(d.timestamp) ? unixMicroToIsoTimestamp(d.timestamp) : d.timestamp,
@@ -56,6 +58,16 @@ const PageLayout: NextPageWithLayout = () => {
selectedInterval.startValue,
selectedInterval.startUnit as dayjs.ManipulateType
)
+
+ const chartData = useFillTimeseriesSorted(
+ normalizedData,
+ 'timestamp',
+ ['avg_execution_time', 'count'],
+ 0,
+ startDate.toISOString(),
+ dayjs().toISOString()
+ )
+
const canReadFunction = checkPermissions(PermissionAction.FUNCTIONS_READ, functionSlug as string)
if (!canReadFunction) {
return
@@ -103,6 +115,7 @@ const PageLayout: NextPageWithLayout = () => {
{
yAxisKey="count"
data={props.data}
highlightedValue={sumBy(props.data, 'count')}
+ customDateFormat={selectedInterval.format}
onBarClick={(v) => {
router.push(
`/project/${projectRef}/functions/${functionSlug}/invocations?its=${startDate.toISOString()}&ite=${
diff --git a/studio/pages/project/[ref]/logs/explorer/index.tsx b/studio/pages/project/[ref]/logs/explorer/index.tsx
index a45ba94cdc3..3b9a7d09f82 100644
--- a/studio/pages/project/[ref]/logs/explorer/index.tsx
+++ b/studio/pages/project/[ref]/logs/explorer/index.tsx
@@ -43,7 +43,7 @@ export const LogsExplorerPage: NextPageWithLayout = () => {
const [warnings, setWarnings] = useState([])
const { data: subscription } = useProjectSubscriptionQuery({ projectRef })
const tier = subscription?.tier
- const [{ params, logData, error, isLoading }, { changeQuery, runQuery, setParams }] =
+ const { params, logData, error, isLoading, changeQuery, runQuery, setParams } =
useLogsQuery(projectRef as string, {
iso_timestamp_start: its ? (its as string) : undefined,
iso_timestamp_end: ite ? (ite as string) : undefined,
@@ -193,7 +193,11 @@ export const LogsExplorerPage: NextPageWithLayout = () => {
-
+
diff --git a/studio/pages/project/[ref]/reports/api-overview.tsx b/studio/pages/project/[ref]/reports/api-overview.tsx
index a1742dbd7ea..2bf633a05b1 100644
--- a/studio/pages/project/[ref]/reports/api-overview.tsx
+++ b/studio/pages/project/[ref]/reports/api-overview.tsx
@@ -141,55 +141,53 @@ const useApiReport = () => {
useEffect(() => {
// update sql for each query
- if (totalRequests[1].changeQuery) {
- totalRequests[1].changeQuery(PRESET_CONFIG.api.queries.totalRequests.sql(filters))
+ if (totalRequests.changeQuery) {
+ totalRequests.changeQuery(PRESET_CONFIG.api.queries.totalRequests.sql(filters))
}
- if (topRoutes[1].changeQuery) {
- topRoutes[1].changeQuery(PRESET_CONFIG.api.queries.topRoutes.sql(filters))
+ if (topRoutes.changeQuery) {
+ topRoutes.changeQuery(PRESET_CONFIG.api.queries.topRoutes.sql(filters))
}
- if (errorCounts[1].changeQuery) {
- errorCounts[1].changeQuery(PRESET_CONFIG.api.queries.errorCounts.sql(filters))
+ if (errorCounts.changeQuery) {
+ errorCounts.changeQuery(PRESET_CONFIG.api.queries.errorCounts.sql(filters))
}
- if (topErrorRoutes[1].changeQuery) {
- topErrorRoutes[1].changeQuery(PRESET_CONFIG.api.queries.topErrorRoutes.sql(filters))
+ if (topErrorRoutes.changeQuery) {
+ topErrorRoutes.changeQuery(PRESET_CONFIG.api.queries.topErrorRoutes.sql(filters))
}
- if (responseSpeed[1].changeQuery) {
- responseSpeed[1].changeQuery(PRESET_CONFIG.api.queries.responseSpeed.sql(filters))
+ if (responseSpeed.changeQuery) {
+ responseSpeed.changeQuery(PRESET_CONFIG.api.queries.responseSpeed.sql(filters))
}
- if (topSlowRoutes[1].changeQuery) {
- topSlowRoutes[1].changeQuery(PRESET_CONFIG.api.queries.topSlowRoutes.sql(filters))
+ if (topSlowRoutes.changeQuery) {
+ topSlowRoutes.changeQuery(PRESET_CONFIG.api.queries.topSlowRoutes.sql(filters))
}
}, [JSON.stringify(filters)])
const handleRefresh = async () => {
- activeHooks.forEach(([_hookData, hookHandler]) => {
- hookHandler.runQuery()
- })
+ activeHooks.forEach(hook => hook.runQuery())
}
const handleSetParams = (params: Partial
) => {
- activeHooks.forEach(([_hookData, hookHandler]) => {
- hookHandler.setParams?.((prev: LogsEndpointParams) => ({ ...prev, ...params }))
+ activeHooks.forEach(hook => {
+ hook.setParams?.((prev: LogsEndpointParams) => ({ ...prev, ...params }))
})
}
- const isLoading = activeHooks.some(([hookData]) => hookData.isLoading)
+ const isLoading = activeHooks.some(hook => hook.isLoading)
return {
data: {
- totalRequests: totalRequests[0].logData,
- errorCounts: errorCounts[0].logData,
- responseSpeed: responseSpeed[0].logData,
- topRoutes: topRoutes[0].logData,
- topErrorRoutes: topErrorRoutes[0].logData,
- topSlowRoutes: topSlowRoutes[0].logData,
+ totalRequests: totalRequests.logData,
+ errorCounts: errorCounts.logData,
+ responseSpeed: responseSpeed.logData,
+ topRoutes: topRoutes.logData,
+ topErrorRoutes: topErrorRoutes.logData,
+ topSlowRoutes: topSlowRoutes.logData,
},
params: {
- totalRequests: totalRequests[0].params,
- errorCounts: errorCounts[0].params,
- responseSpeed: responseSpeed[0].params,
- topRoutes: topRoutes[0].params,
- topErrorRoutes: topErrorRoutes[0].params,
- topSlowRoutes: topSlowRoutes[0].params,
+ totalRequests: totalRequests.params,
+ errorCounts: errorCounts.params,
+ responseSpeed: responseSpeed.params,
+ topRoutes: topRoutes.params,
+ topErrorRoutes: topErrorRoutes.params,
+ topSlowRoutes: topSlowRoutes.params,
},
mergeParams: handleSetParams,
filters,
diff --git a/studio/pages/project/[ref]/reports/index.tsx b/studio/pages/project/[ref]/reports/index.tsx
index dfda77f3db6..a533b467837 100644
--- a/studio/pages/project/[ref]/reports/index.tsx
+++ b/studio/pages/project/[ref]/reports/index.tsx
@@ -5,11 +5,11 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
import { NextPageWithLayout } from 'types'
import { checkPermissions, useFlag, useStore } from 'hooks'
+import useProfile from 'hooks/misc/useProfile'
import { useParams } from 'common/hooks'
import { post } from 'lib/common/fetch'
import { API_URL, PROJECT_STATUS } from 'lib/constants'
import { useProjectContentStore } from 'stores/projectContentStore'
-import { useProfileQuery } from 'data/profile/profile-query'
import Loading from 'components/ui/Loading'
import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState'
import { createReport } from 'components/to-be-cleaned/Reports/Reports.utils'
@@ -21,7 +21,7 @@ export const UserReportPage: NextPageWithLayout = () => {
const router = useRouter()
const { ref } = useParams()
- const { data: profile } = useProfileQuery()
+ const { data: profile } = useProfile()
const { ui } = useStore()
const project = ui.selectedProject
diff --git a/studio/pages/project/[ref]/reports/query-performance.tsx b/studio/pages/project/[ref]/reports/query-performance.tsx
index 615b8b016b3..e23ede36487 100644
--- a/studio/pages/project/[ref]/reports/query-performance.tsx
+++ b/studio/pages/project/[ref]/reports/query-performance.tsx
@@ -31,17 +31,17 @@ const QueryPerformanceReport: NextPageWithLayout = () => {
const queryHitRate = hooks.queryHitRate()
const isLoading = [
- mostFrequentlyInvoked[0].isLoading,
- mostTimeConsuming[0].isLoading,
- slowestExecutionTime[0].isLoading,
- queryHitRate[0].isLoading,
+ mostFrequentlyInvoked.isLoading,
+ mostTimeConsuming.isLoading,
+ slowestExecutionTime.isLoading,
+ queryHitRate.isLoading,
].every((value) => value)
const handleRefresh = async () => {
- mostFrequentlyInvoked[1].runQuery()
- mostTimeConsuming[1].runQuery()
- slowestExecutionTime[1].runQuery()
- queryHitRate[1].runQuery()
+ mostFrequentlyInvoked.runQuery()
+ mostTimeConsuming.runQuery()
+ slowestExecutionTime.runQuery()
+ queryHitRate.runQuery()
}
const checkAlert = (
@@ -60,8 +60,8 @@ const QueryPerformanceReport: NextPageWithLayout = () => {
)
- const indexHitRate = queryHitRate[0]?.data?.[0]?.ratio
- const tableHitRate = queryHitRate[0]?.data?.[1]?.ratio
+ const indexHitRate = queryHitRate.data?.[0]?.ratio
+ const tableHitRate = queryHitRate.data?.[1]?.ratio
const showIndexWarning =
indexHitRate && tableHitRate && (indexHitRate <= 0.99 || tableHitRate <= 0.99)
@@ -130,7 +130,7 @@ const QueryPerformanceReport: NextPageWithLayout = () => {
: dangerAlert}
- {(queryHitRate[0]?.data![0]?.ratio * 100).toFixed(2)}
+ {(queryHitRate?.data![0]?.ratio * 100).toFixed(2)}
%
@@ -138,7 +138,7 @@ const QueryPerformanceReport: NextPageWithLayout = () => {
- {queryHitRate[0]?.data![1]?.name == 'table hit rate' && 'Table Hit Rate'}
+ {queryHitRate?.data![1]?.name == 'table hit rate' && 'Table Hit Rate'}
{tableHitRate >= 0.99
? checkAlert
@@ -147,7 +147,7 @@ const QueryPerformanceReport: NextPageWithLayout = () => {
: dangerAlert}
- {(queryHitRate[0]?.data![1]?.ratio * 100).toFixed(2)}
+ {(queryHitRate?.data![1]?.ratio * 100).toFixed(2)}
%
@@ -218,8 +218,8 @@ const QueryPerformanceReport: NextPageWithLayout = () => {
>
}
body={
- !isLoading && mostTimeConsuming && mostTimeConsuming[0]?.data ? (
- mostTimeConsuming[0].data!.map((item, i) => {
+ !isLoading && mostTimeConsuming && mostTimeConsuming?.data ? (
+ mostTimeConsuming?.data!.map((item, i) => {
return (
@@ -273,8 +273,8 @@ const QueryPerformanceReport: NextPageWithLayout = () => {
>
}
body={
- !isLoading && mostFrequentlyInvoked && mostFrequentlyInvoked[0]?.data ? (
- mostFrequentlyInvoked[0].data!.map((item, i) => {
+ !isLoading && mostFrequentlyInvoked && mostFrequentlyInvoked?.data ? (
+ mostFrequentlyInvoked.data!.map((item, i) => {
return (
@@ -336,8 +336,8 @@ const QueryPerformanceReport: NextPageWithLayout = () => {
>
}
body={
- !isLoading && slowestExecutionTime && slowestExecutionTime[0]?.data ? (
- slowestExecutionTime[0].data!.map((item, i) => {
+ !isLoading && slowestExecutionTime && slowestExecutionTime?.data ? (
+ slowestExecutionTime.data!.map((item, i) => {
return (
diff --git a/studio/state/sql-editor.ts b/studio/state/sql-editor.ts
index ef64a8f620d..49833b3d8f1 100644
--- a/studio/state/sql-editor.ts
+++ b/studio/state/sql-editor.ts
@@ -37,21 +37,25 @@ export const sqlEditorState = proxy({
[key: string]: 'IDLE' | 'UPDATING' | 'UPDATING_FAILED'
},
+ orderSnippets: (snippets: SqlSnippet[]) => {
+ return (
+ snippets
+ .filter((s) => Boolean(s.id))
+ // first alphabetical
+ .sort((a, b) => a.name?.localeCompare(b.name))
+ )
+ },
+ reorderSnippets: (projectRef: string) => {
+ sqlEditorState.orders[projectRef] = sqlEditorState
+ .orderSnippets(
+ sqlEditorState.orders[projectRef].map((id) => sqlEditorState.snippets[id].snippet)
+ )
+ .map((s) => s.id!)
+ },
+
setRemoteSnippets: (snippets: SqlSnippet[], projectRef: string) => {
if (!sqlEditorState.orders[projectRef]) {
- const orderedSnippets = snippets
- .filter((s) => Boolean(s.id))
- .sort((a, b) => {
- // sort by snippet.inserted_at, falling back to snippet.id
- if (a.inserted_at && b.inserted_at) {
- const aDate = new Date(a.inserted_at)
- const bDate = new Date(b.inserted_at)
- // sort by date
- return aDate > bDate ? -1 : 1
- }
-
- return a.id! > b.id! ? -1 : 1
- })
+ const orderedSnippets = sqlEditorState.orderSnippets(snippets)
sqlEditorState.orders[projectRef] = orderedSnippets.map((s) => s.id!)
}
@@ -74,6 +78,7 @@ export const sqlEditorState = proxy({
!sqlEditorState.orders[projectRef].includes(snippet.id)
) {
sqlEditorState.orders[projectRef].unshift(snippet.id)
+ sqlEditorState.reorderSnippets(projectRef)
}
if (isNew) {
sqlEditorState.needsCreating.add(snippet.id)
@@ -116,6 +121,17 @@ export const sqlEditorState = proxy({
sqlEditorState.needsSaving.add(id)
}
},
+ renameSnippet: (id: string, name: string, description?: string) => {
+ if (sqlEditorState.snippets[id]) {
+ const { snippet, projectRef } = sqlEditorState.snippets[id]
+
+ snippet.name = name
+ snippet.description = description
+
+ sqlEditorState.reorderSnippets(projectRef)
+ sqlEditorState.needsSaving.add(id)
+ }
+ },
addNeedsSaving: (id: string) => {
sqlEditorState.needsSaving.add(id)
},
@@ -214,6 +230,8 @@ if (typeof window !== 'undefined') {
content: { ...snippet.snippet.content, content_id: id },
type: 'sql',
id,
+ name: snippet.snippet.name,
+ description: snippet.snippet.description,
})
sqlEditorState.needsSaving.delete(id)
diff --git a/studio/tests/components/Home/ProjectUsage.test.js b/studio/tests/components/Home/ProjectUsage.test.js
index dabfd3133f3..159e62052d5 100644
--- a/studio/tests/components/Home/ProjectUsage.test.js
+++ b/studio/tests/components/Home/ProjectUsage.test.js
@@ -1,17 +1,8 @@
import { waitFor, screen } from '@testing-library/react'
-import { render } from '../../helpers'
+import { clickDropdown, render } from '../../helpers'
import userEvent from '@testing-library/user-event'
-jest.mock('lib/common/fetch')
-import { get } from 'lib/common/fetch'
-
-// mock the router
-jest.mock('next/router')
-import { useRouter } from 'next/router'
-import { clickDropdown } from 'tests/helpers'
-const mockPush = jest.fn()
-useRouter.mockReturnValue({ query: { ref: '123' }, push: mockPush })
-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import { get } from 'lib/common/fetch'
// TODO: abstract out to global setup
const ProjectUsage = jest.fn()
@@ -40,49 +31,35 @@ ProjectUsage.mockImplementation((props) => {
)
})
-jest.mock('components/ui/Flag/Flag')
-import Flag from 'components/ui/Flag/Flag'
-Flag.mockImplementation(({ children }) => <>{children}>)
-jest.mock('hooks')
-import { useFlag } from 'hooks'
jest.mock('data/subscriptions/project-subscription-query')
import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query'
-useFlag.mockReturnValue(true)
useProjectSubscriptionQuery.mockReturnValue({
data: undefined,
})
beforeEach(() => {
get.mockReset()
- mockPush.mockReset()
})
const MOCK_CHART_DATA = {
- data: [
+ result: [
{
total_auth_requests: 123,
total_realtime_requests: 223,
total_storage_requests: 323,
total_rest_requests: 333,
- timestamp: new Date().getTime() / 1000,
+ timestamp: new Date().toISOString(),
},
- ],
- total: 123 + 223 + 323,
- totalAverage: 123 + 223 + 323,
- totalGrouped: {
- total_auth_requests: 123,
- total_realtime_requests: 223,
- total_storage_requests: 323,
- total_rest_requests: 333,
- },
+ ]
}
test('mounts correctly', async () => {
get.mockImplementation((url) => {
- if (url.includes('usage')) return {}
- if (url.includes('subscription')) return {}
- return { data: MOCK_CHART_DATA }
+ if (decodeURIComponent(url).includes('usage.api-counts')) {
+ return MOCK_CHART_DATA
+ }
+ return {}
})
render()
await waitFor(() => screen.getByText(/Statistics for past 24 hours/))
@@ -94,8 +71,10 @@ test('mounts correctly', async () => {
test('dropdown options changes chart query', async () => {
get.mockImplementation((url) => {
- if (url.includes('usage')) return {}
- return { data: MOCK_CHART_DATA }
+ if (decodeURIComponent(url).includes('usage.api-counts')) {
+ return MOCK_CHART_DATA
+ }
+ return {}
})
render()
await waitFor(() => screen.getByText(/Statistics for past 24 hours/))
diff --git a/studio/tests/pages/projects/LogTable.test.js b/studio/tests/pages/projects/LogTable.test.js
index 1a7430f9630..5e90b4b5482 100644
--- a/studio/tests/pages/projects/LogTable.test.js
+++ b/studio/tests/pages/projects/LogTable.test.js
@@ -68,6 +68,18 @@ test('can display standard preview table columns', async () => {
await expect(screen.findByText(fakeMicroTimestamp)).rejects.toThrow()
})
+test("closes the selection if the selected row's data changes", async () => {
+ const { rerender } = render(
+
+ )
+ const text = await screen.findByText(/some event message/)
+ userEvent.click(text)
+ await screen.findByText('Copy')
+ rerender()
+ await expect(screen.findByText(/some event message/)).rejects.toThrow()
+ await screen.findByText(/some other message/)
+})
+
test.each([
{
queryType: 'functions',
@@ -119,6 +131,9 @@ test.each([
data: [
{
event_message: JSON.stringify({ msg: 'some message', path: '/auth-path', level: 'info' }),
+ msg: 'some message',
+ path: '/auth-path',
+ level: 'info',
timestamp: 1659545029083869,
id: '4475cf6f-2929-4296-ab44-ce2c17069937',
},
diff --git a/studio/tests/pages/projects/Logs.utils.test.js b/studio/tests/pages/projects/Logs.utils.test.js
index b705fa4c729..499cb7d8256 100644
--- a/studio/tests/pages/projects/Logs.utils.test.js
+++ b/studio/tests/pages/projects/Logs.utils.test.js
@@ -144,6 +144,19 @@ test.each([
{ timestamp: '2023-04-26T20:00:00.000Z', count: 0 },
],
},
+ // fill multiple keys in one go
+ {
+ case: 'fill beyond min/max',
+ data: [],
+ len: 2,
+ min: '2023-04-26T18:00:00.000Z',
+ max: '2023-04-26T19:00:00.000Z',
+ valKey: ['count', 'other'],
+ includes: [
+ { timestamp: '2023-04-26T18:00:00.000Z', count: 0, other: 0 },
+ { timestamp: '2023-04-26T19:00:00.000Z', count: 0, other: 0 },
+ ],
+ },
])(
'fillTimeseries : $case',
({ data, len, includes, min, max, tsKey = 'timestamp', valKey = 'count', defaultVal = 0 }) => {
diff --git a/studio/tests/pages/projects/LogsPreviewer.test.js b/studio/tests/pages/projects/LogsPreviewer.test.js
index 67883b53929..ceb210b9e17 100644
--- a/studio/tests/pages/projects/LogsPreviewer.test.js
+++ b/studio/tests/pages/projects/LogsPreviewer.test.js
@@ -87,25 +87,27 @@ test.each([
queryType: 'auth',
tableName: undefined,
tableLog: logDataFixture({
- event_message: JSON.stringify({
- msg: 'some message',
- path: '/auth-path',
- level: 'info',
- status: 300,
- }),
+ event_message: "some event_message",
+ level: "info",
+ path: '/auth-path',
+ msg: 'some metadata_msg',
+ level: 'info',
+ status: 300,
+ metadata: undefined
}),
selectionLog: logDataFixture({
- event_message: JSON.stringify({
- msg: 'some message',
+ event_message: "some event_message",
+ metadata: {
+ msg: 'some metadata_msg',
path: '/auth-path',
level: 'info',
status: 300,
- }),
+ }
}),
- tableTexts: [/auth\-path/, /some message/, /INFO/],
+ tableTexts: [/auth\-path/, /some metadata_msg/, /INFO/],
selectionTexts: [
/auth\-path/,
- /some message/,
+ /some metadata_msg/,
/INFO/,
/300/,
/Timestamp/,
diff --git a/studio/tests/pages/projects/logs-query.test.js b/studio/tests/pages/projects/logs-query.test.js
index eb1ce4d5c50..7fd180206d9 100644
--- a/studio/tests/pages/projects/logs-query.test.js
+++ b/studio/tests/pages/projects/logs-query.test.js
@@ -131,6 +131,7 @@ test('custom sql querying', async () => {
expect(get).toHaveBeenCalledWith(expect.stringContaining('sql='), expect.anything())
expect(get).toHaveBeenCalledWith(expect.stringContaining('select'), expect.anything())
expect(get).toHaveBeenCalledWith(expect.stringContaining('edge_logs'), expect.anything())
+ expect(get).toHaveBeenCalledWith(expect.stringContaining(encodeURIComponent("my_count")), expect.anything())
expect(get).toHaveBeenCalledWith(
expect.stringContaining('iso_timestamp_start'),
expect.anything()
@@ -159,6 +160,40 @@ test('custom sql querying', async () => {
await expect(screen.findByText(/Load older/)).rejects.toThrow()
})
+test("bug: can edit query after selecting a log", async ()=>{
+ get.mockImplementation((url) => {
+ if (url.includes('sql=') && url.includes('select') && !url.includes("limit 222")) {
+ return {
+ result: [ { my_count: 12345 }],
+ }
+ }
+ return { result: [] }
+ })
+ const { container } = render()
+ // run default query
+ userEvent.click(await screen.findByText('Run'))
+ const rowValue = await screen.findByText(/12345/) // row value
+ // open up an show selection panel
+ await userEvent.click(rowValue)
+ await screen.findByText(/Copy/)
+
+ // change the query
+ let editor = container.querySelector('.monaco-editor')
+ // type new query
+ userEvent.click(editor)
+ userEvent.type(editor, ' something')
+ userEvent.click(await screen.findByText('Run'))
+
+ await waitFor(
+ () => {
+ expect(get).toHaveBeenCalledWith(expect.stringContaining(encodeURIComponent("something")), expect.anything())
+ },
+ { timeout: 1000 }
+ )
+ // closes the selection panel
+ await expect(screen.findByText(/Copy/)).rejects.toThrow()
+})
+
test('query warnings', async () => {
const router = defaultRouterMock()
router.query = {
diff --git a/studio/types/ui.ts b/studio/types/ui.ts
index 114f8bac3cb..7ed014c462c 100644
--- a/studio/types/ui.ts
+++ b/studio/types/ui.ts
@@ -19,8 +19,8 @@ export interface ChartIntervals {
key: 'minutely' | 'hourly' | 'daily' | '5min' | '15min' | '1hr' | '1day' | '7day'
label: string
startValue: number
- startUnit: 'min' | 'hour' | 'day'
- format?: 'MMM D, h:mma' | 'MMM D, ha' | 'MMM D'
+ startUnit: 'minute' | 'hour' | 'day'
+ format?: 'MMM D, h:mm:ssa' | 'MMM D, h:mma' | 'MMM D, ha' | 'MMM D'
}
export interface VaultSecret {
diff --git a/supabase/functions/ai-docs/index.ts b/supabase/functions/ai-docs/index.ts
index 345553687fc..9c83a9c9fa2 100644
--- a/supabase/functions/ai-docs/index.ts
+++ b/supabase/functions/ai-docs/index.ts
@@ -198,6 +198,9 @@ serve(async (req) => {
${oneLine`
- Prefer splitting your response into multiple paragraphs.
`}
+ ${oneLine`
+ - Respond using the same language as the question.
+ `}
${oneLine`
- Output as markdown.
`}