mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 15:19:08 +03:00
Merge branch 'master' into feat/embedded
This commit is contained in:
commit
5a24c00324
1042 files changed
+60527
-27686
No files matched your search
+1
-2
@@ -1,5 +1,6 @@
|
||||
/packages/shared-data/pricing.ts @roryw10 @kevcodez
|
||||
/packages/shared-data/plans.ts @roryw10 @kevcodez
|
||||
/packages/common/telemetry-constants.ts @4L3k51 @loong @pamelachia
|
||||
|
||||
/apps/studio/ @supabase/Dashboard
|
||||
|
||||
@@ -17,5 +18,3 @@
|
||||
/apps/studio/components/interfaces/Organization/BillingSettings/ @supabase/security
|
||||
/apps/studio/components/interfaces/Organization/Documents/ @supabase/security
|
||||
/apps/studio/pages/new/index.tsx @supabase/security
|
||||
|
||||
/apps/studio/lib/constants/telemetry.ts @4L3k51 @loong @pamelachia
|
||||
@@ -29,17 +29,22 @@ jobs:
|
||||
sparse-checkout: |
|
||||
apps/docs
|
||||
|
||||
- name: Set up Node.js
|
||||
- uses: pnpm/action-setup@v4
|
||||
name: Install pnpm
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
run: pnpm i
|
||||
|
||||
- name: Run Troubleshooting script
|
||||
run: npm --prefix=apps/docs run troubleshooting:sync
|
||||
run: pnpm run -F docs troubleshooting:sync
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
name: Playwright Tests
|
||||
name: Local Studio Tests
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'apps/studio/**'
|
||||
- 'tests/local-studio-tests/**'
|
||||
- 'pnpm-lock.yaml'
|
||||
pull_request:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'apps/studio/**'
|
||||
- 'tests/local-studio-tests/**'
|
||||
- 'pnpm-lock.yaml'
|
||||
|
||||
# Cancel old builds on new commit for same workflow + branch/PR
|
||||
@@ -37,14 +39,14 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm i
|
||||
- name: Install Playwright Browsers
|
||||
run: pnpm -C playwright-tests exec playwright install --with-deps
|
||||
run: pnpm -C tests/local-studio-tests exec playwright install --with-deps
|
||||
- name: Run Playwright tests
|
||||
run: pnpm run test:playwright
|
||||
run: pnpm test:local-studio
|
||||
# mark the action as succeeded even if the tests failed. This is temporarily until we make the tests more stable.
|
||||
# continue-on-error: true
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-tests/playwright-report/
|
||||
path: tests/local-studio-tests/playwright-report/
|
||||
retention-days: 7
|
||||
+2
-3
@@ -9,13 +9,12 @@ apps/**/out
|
||||
apps/www/schema.sql
|
||||
apps/www/public/images/*
|
||||
apps/docs/**/generated/*
|
||||
apps/docs/examples
|
||||
examples/slack-clone/nextjs-slack-clone/full-schema.sql
|
||||
# ignore files with custom js formatting
|
||||
apps/docs/pages/guides/auth/*.mdx
|
||||
apps/docs/pages/guides/integrations/*.mdx
|
||||
apps/studio/public
|
||||
apps/**/.turbo
|
||||
apps/docs/CONTRIBUTING.md
|
||||
apps/design-system/__registry__
|
||||
packages/icons/__registry__
|
||||
packages/icons/src/icons/*.ts
|
||||
packages/icons/src/icons/*.ts
|
||||
@@ -52,10 +52,10 @@ You can also [self-host](https://supabase.com/docs/guides/hosting/overview) and
|
||||
|
||||
- [Postgres](https://www.postgresql.org/) is an object-relational database system with over 30 years of active development that has earned it a strong reputation for reliability, feature robustness, and performance.
|
||||
- [Realtime](https://github.com/supabase/realtime) is an Elixir server that allows you to listen to PostgreSQL inserts, updates, and deletes using websockets. Realtime polls Postgres' built-in replication functionality for database changes, converts changes to JSON, then broadcasts the JSON over websockets to authorized clients.
|
||||
- [PostgREST](http://postgrest.org/) is a web server that turns your PostgreSQL database directly into a RESTful API
|
||||
- [PostgREST](http://postgrest.org/) is a web server that turns your PostgreSQL database directly into a RESTful API.
|
||||
- [GoTrue](https://github.com/supabase/gotrue) is a JWT based API for managing users and issuing JWT tokens.
|
||||
- [Storage](https://github.com/supabase/storage-api) provides a RESTful interface for managing Files stored in S3, using Postgres to manage permissions.
|
||||
- [pg_graphql](http://github.com/supabase/pg_graphql/) a PostgreSQL extension that exposes a GraphQL API
|
||||
- [pg_graphql](http://github.com/supabase/pg_graphql/) a PostgreSQL extension that exposes a GraphQL API.
|
||||
- [postgres-meta](https://github.com/supabase/postgres-meta) is a RESTful API for managing your Postgres, allowing you to fetch tables, add roles, and run queries, etc.
|
||||
- [Kong](https://github.com/Kong/kong) is a cloud-native API gateway.
|
||||
|
||||
|
||||
@@ -2161,6 +2161,17 @@ export const Index: Record<string, any> = {
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"logs-bar-chart": {
|
||||
name: "logs-bar-chart",
|
||||
type: "components:example",
|
||||
registryDependencies: undefined,
|
||||
component: React.lazy(() => import("@/registry/default/example/logs-bar-chart")),
|
||||
source: "",
|
||||
files: ["registry/default/example/logs-bar-chart.tsx"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
},
|
||||
"chart-area-axes": {
|
||||
name: "chart-area-axes",
|
||||
type: "components:block",
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { Provider as JotaiProvider } from 'jotai'
|
||||
import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
||||
import { ThemeProviderProps } from 'next-themes/dist/types'
|
||||
|
||||
import { TooltipProvider_Shadcn_ } from 'ui'
|
||||
import { TooltipProvider } from 'ui'
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
return (
|
||||
<JotaiProvider>
|
||||
<NextThemesProvider {...props}>
|
||||
<TooltipProvider_Shadcn_ delayDuration={0}>{children}</TooltipProvider_Shadcn_>
|
||||
<TooltipProvider delayDuration={0}>{children}</TooltipProvider>
|
||||
</NextThemesProvider>
|
||||
</JotaiProvider>
|
||||
)
|
||||
|
||||
@@ -117,6 +117,11 @@ export const docsConfig: DocsConfig = {
|
||||
href: '/docs/fragments/multi-select',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
title: 'Logs Bar Chart',
|
||||
href: '/docs/fragments/logs-bar-chart',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Logs Bar Chart
|
||||
description: A stacked bar chart that displays logs errors and successes.
|
||||
fragment: true
|
||||
---
|
||||
|
||||
<ComponentPreview name="logs-bar-chart" peekCode wide />
|
||||
|
||||
## Usage
|
||||
|
||||
This component is used to display the bar chart in logs pages.
|
||||
|
||||
It requires that the `data` prop is an array of objects with the following shape:
|
||||
|
||||
```ts
|
||||
type LogsBarChartDatum = {
|
||||
timestamp: string
|
||||
ok_count: number
|
||||
error_count: number
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
import { LogsBarChart } from 'ui-patterns/LogsBarChart'
|
||||
|
||||
export default function LogsBarChartDemo() {
|
||||
const data = Array.from({ length: 100 }, (_, i) => {
|
||||
const date = new Date()
|
||||
date.setMinutes(date.getMinutes() - i * 5) // Each point 5 minutes apart
|
||||
|
||||
return {
|
||||
timestamp: date.toISOString(),
|
||||
ok_count: Math.floor(Math.random() * 100), // Random value 0-99
|
||||
error_count: Math.floor(Math.random() * 50), // Random value 0-50
|
||||
warning_count: Math.floor(Math.random() * 50), // Random value 0-50
|
||||
}
|
||||
}).reverse()
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<LogsBarChart data={data} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,22 +1,16 @@
|
||||
import { Button } from 'ui'
|
||||
import {
|
||||
Tooltip_Shadcn_,
|
||||
TooltipContent_Shadcn_,
|
||||
TooltipProvider_Shadcn_,
|
||||
TooltipTrigger_Shadcn_,
|
||||
} from 'ui'
|
||||
import { Button, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from 'ui'
|
||||
|
||||
export default function TooltipDemo() {
|
||||
return (
|
||||
<TooltipProvider_Shadcn_>
|
||||
<Tooltip_Shadcn_>
|
||||
<TooltipTrigger_Shadcn_ asChild>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button type="outline">Hover</Button>
|
||||
</TooltipTrigger_Shadcn_>
|
||||
<TooltipContent_Shadcn_>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>Add to library</p>
|
||||
</TooltipContent_Shadcn_>
|
||||
</Tooltip_Shadcn_>
|
||||
</TooltipProvider_Shadcn_>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
@@ -1201,4 +1201,9 @@ export const examples: Registry = [
|
||||
type: 'docs:example',
|
||||
files: ['example/expanding-textarea-demo.tsx'],
|
||||
},
|
||||
{
|
||||
name: 'logs-bar-chart',
|
||||
type: 'components:example',
|
||||
files: ['example/logs-bar-chart.tsx'],
|
||||
},
|
||||
]
|
||||
@@ -26,3 +26,6 @@ yarn-error.log*
|
||||
|
||||
# Sitemap
|
||||
public/sitemap.xml
|
||||
|
||||
# Copied examples folder
|
||||
/examples/
|
||||
@@ -1,21 +0,0 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug: string[] }
|
||||
|
||||
const GuidePage = async ({ params }: { params: Params }) => {
|
||||
const data = await getGuidesMarkdown(params)
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams()
|
||||
const generateMetadata = genGuideMeta(getGuidesMarkdown)
|
||||
|
||||
export default GuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -1,61 +0,0 @@
|
||||
import { cache, type PropsWithChildren } from 'react'
|
||||
|
||||
import { IS_PLATFORM } from 'common'
|
||||
|
||||
import { NavMenuSection } from '~/components/Navigation/Navigation.types'
|
||||
import Layout from '~/layouts/guides'
|
||||
import { supabaseMisc } from '~/lib/supabaseMisc'
|
||||
import { getAiPrompts } from './getting-started/ai-prompts/[slug]/AiPrompts.utils'
|
||||
|
||||
// Revalidate occasionally to pick up changes to partners
|
||||
// 60 seconds/minute * 60 minutes/hour * 24 hours/day
|
||||
// export const revalidate = 86_400
|
||||
|
||||
const GuidesLayout = async ({ children }: PropsWithChildren) => {
|
||||
const [partnerNavItems, promptNavItems] = await Promise.all([getPartners(), getPrompts()])
|
||||
|
||||
const additionalNavItems =
|
||||
partnerNavItems.length > 0 || promptNavItems.length > 0
|
||||
? { integrations: partnerNavItems, prompts: promptNavItems }
|
||||
: undefined
|
||||
|
||||
return <Layout additionalNavItems={additionalNavItems}>{children}</Layout>
|
||||
}
|
||||
|
||||
async function getPrompts() {
|
||||
const prompts = await getAiPrompts()
|
||||
return prompts.map(
|
||||
(prompt) =>
|
||||
({
|
||||
name: prompt.heading,
|
||||
url: `/guides/getting-started/ai-prompts/${prompt.filename}`,
|
||||
}) as Partial<NavMenuSection>
|
||||
)
|
||||
}
|
||||
|
||||
const getPartners = cache(getPartnersImpl)
|
||||
async function getPartnersImpl() {
|
||||
if (!IS_PLATFORM) return []
|
||||
|
||||
const { data, error } = await supabaseMisc()
|
||||
.from('partners')
|
||||
.select('slug, title')
|
||||
.eq('approved', true)
|
||||
.eq('type', 'technology')
|
||||
.order('title')
|
||||
if (error) {
|
||||
console.error(new Error('Error fetching partners', { cause: error }))
|
||||
}
|
||||
|
||||
const partnerNavItems = (data ?? []).map(
|
||||
(partner) =>
|
||||
({
|
||||
name: partner.title,
|
||||
url: `https://supabase.com/partners/integrations/${partner.slug}` as `https://${string}`,
|
||||
}) as Partial<NavMenuSection>
|
||||
)
|
||||
|
||||
return partnerNavItems
|
||||
}
|
||||
|
||||
export default GuidesLayout
|
||||
@@ -1,106 +0,0 @@
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import { CodeBlock } from 'ui'
|
||||
import { Heading } from 'ui/src/components/CustomHTMLElements'
|
||||
import { type TOCHeader } from '~/components/GuidesTableOfContents'
|
||||
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import type { Parameter } from '~/lib/refGenerator/refTypes'
|
||||
import specFile from '~/spec/cli_v1_config.yaml' with { type: 'yml' }
|
||||
|
||||
const meta = {
|
||||
title: 'Supabase CLI config',
|
||||
}
|
||||
|
||||
const generateMetadata = genGuideMeta(() => ({
|
||||
pathname: '/guides/cli/config',
|
||||
meta,
|
||||
}))
|
||||
|
||||
const tocList: TOCHeader[] = []
|
||||
const content = specFile.info.tags.map((tag: { id: string; title: string }, id: number) => {
|
||||
tocList.push({ id: `${id}`, text: tag.title, link: `${tag.id}-config`, level: 2 })
|
||||
return (
|
||||
<div>
|
||||
<Heading tag="h2">{tag.title} Config</Heading>
|
||||
{specFile.parameters
|
||||
.filter((param: Parameter) => param.tags && param.tags[0] === tag.id)
|
||||
.map((parameter: Parameter, id: number) => {
|
||||
tocList.push({ id: `${id}`, text: parameter.id, link: `#${parameter.id}`, level: 3 })
|
||||
return <Info parameter={parameter} />
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
const Config = () => {
|
||||
const editLink = newEditLink('supabase/supabase/blob/master/apps/docs/spec/cli_v1_config.yaml')
|
||||
|
||||
return (
|
||||
<GuideTemplate meta={meta} editLink={editLink}>
|
||||
<ReactMarkdown>{specFile.info.description}</ReactMarkdown>
|
||||
<div>{content}</div>
|
||||
</GuideTemplate>
|
||||
)
|
||||
}
|
||||
|
||||
function Info({ parameter }: { parameter: Parameter }) {
|
||||
return (
|
||||
<div className="mt-8">
|
||||
<div>
|
||||
<Heading tag="h3" parseAnchors={false} customAnchor={parameter.id}>
|
||||
<code>{parameter.title}</code>
|
||||
</Heading>
|
||||
|
||||
<div className="border-b pb-8" key={parameter.id}>
|
||||
<div className=" mb-16">
|
||||
<div>
|
||||
<table className="table-auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-left">Name</th>
|
||||
<th className="text-left">Default</th>
|
||||
<th className="text-left">Required</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{parameter.id}</td>
|
||||
<td>{parameter.default ? parameter.default.toString() : 'None'}</td>
|
||||
<td>{parameter.required !== undefined && parameter.required.toString()}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="mb-4 scroll-mt-16 mt-0 ">
|
||||
<p className="text-sm font-bold uppercase">Description</p>
|
||||
<ReactMarkdown>{parameter.description}</ReactMarkdown>
|
||||
</div>
|
||||
{parameter.usage && (
|
||||
<div className="mb-4 scroll-mt-16 mt-0 ">
|
||||
<p className="text-sm font-bold uppercase">Usage</p>
|
||||
<CodeBlock className="useless-code-block-class" language="py">
|
||||
{parameter.usage}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
)}
|
||||
{parameter.links && (
|
||||
<div>
|
||||
<p className="text-sm font-bold uppercase">See also</p>
|
||||
<ul>
|
||||
{parameter.links.map((link) => (
|
||||
<li key={link.link}>
|
||||
<a href={link.link}>{link.name}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Config
|
||||
export { generateMetadata }
|
||||
@@ -1,146 +0,0 @@
|
||||
import matter from 'gray-matter'
|
||||
import { type SerializeOptions } from 'next-mdx-remote/dist/types'
|
||||
import { notFound } from 'next/navigation'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
|
||||
import { genGuideMeta, removeRedundantH1 } from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import { fetchRevalidatePerDay } from '~/features/helpers.fetch'
|
||||
import { isValidGuideFrontmatter } from '~/lib/docs'
|
||||
import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform'
|
||||
import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
|
||||
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
|
||||
import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
|
||||
import {
|
||||
terraformDocsBranch,
|
||||
terraformDocsDocsDir,
|
||||
terraformDocsOrg,
|
||||
terraformDocsRepo,
|
||||
} from '../terraformConstants'
|
||||
|
||||
// Each external docs page is mapped to a local page
|
||||
const pageMap = [
|
||||
{
|
||||
remoteFile: 'README.md',
|
||||
meta: {
|
||||
title: 'Terraform Provider',
|
||||
},
|
||||
useRoot: true,
|
||||
},
|
||||
{
|
||||
slug: 'tutorial',
|
||||
remoteFile: 'tutorial.md',
|
||||
meta: {
|
||||
title: 'Using the Supabase Terraform Provider',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
interface Params {
|
||||
slug?: string[]
|
||||
}
|
||||
|
||||
const TerraformDocs = async ({ params }: { params: Params }) => {
|
||||
const { meta, ...data } = await getContent(params)
|
||||
|
||||
const options = {
|
||||
mdxOptions: {
|
||||
remarkPlugins: [remarkMkDocsAdmonition, remarkPyMdownTabs, [removeTitle, meta.title]],
|
||||
rehypePlugins: [[linkTransform, urlTransform], rehypeSlug],
|
||||
},
|
||||
} as SerializeOptions
|
||||
|
||||
return <GuideTemplate mdxOptions={options} meta={meta} {...data} />
|
||||
}
|
||||
|
||||
/**
|
||||
* The GitHub repo uses relative links, which don't lead to the right locations
|
||||
* in docs.
|
||||
*
|
||||
* @param url The original link, as written in the Markdown file
|
||||
* @returns The rewritten link
|
||||
*/
|
||||
const urlTransform: UrlTransformFunction = (url: string) => {
|
||||
try {
|
||||
const placeholderHostname = 'placeholder'
|
||||
const { hostname, pathname, hash } = new URL(url, `http://${placeholderHostname}`)
|
||||
|
||||
// Don't modify a url with a FQDN or a url that's only a hash
|
||||
if (hostname !== placeholderHostname || pathname === '/') {
|
||||
return url
|
||||
}
|
||||
|
||||
const getBasename = (pathname: string) =>
|
||||
pathname.endsWith('.md') ? pathname.replace(/\.md$/, '') : pathname
|
||||
const stripLeadingPrefix = (pathname: string) => pathname.replace(/^\//, '')
|
||||
const stripLeadingDocs = (pathname: string) => pathname.replace(/^docs\//, '')
|
||||
|
||||
const relativePath = stripLeadingPrefix(getBasename(pathname))
|
||||
|
||||
const page = pageMap.find(
|
||||
({ remoteFile, useRoot }) =>
|
||||
(useRoot && `${relativePath}.md` === remoteFile) ||
|
||||
(!useRoot && `${stripLeadingDocs(relativePath)}.md` === remoteFile)
|
||||
)
|
||||
|
||||
if (page) {
|
||||
return 'terraform' + `/${page.slug}` + hash
|
||||
}
|
||||
|
||||
// If we don't have this page in our docs, link to GitHub repo
|
||||
return `https://github.com/${terraformDocsOrg}/${terraformDocsRepo}/blob/${terraformDocsBranch}${pathname}${hash}`
|
||||
} catch (err) {
|
||||
console.error('Error transforming markdown URL', err)
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch markdown from external repo
|
||||
*/
|
||||
const getContent = async ({ slug }: Params) => {
|
||||
const [requestedSlug] = slug ?? []
|
||||
const page = pageMap.find((page) => page.slug === requestedSlug)
|
||||
|
||||
if (!page) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
const { meta, remoteFile, useRoot } = page
|
||||
|
||||
const editLink = newEditLink(
|
||||
`${terraformDocsOrg}/${terraformDocsRepo}/blob/${terraformDocsBranch}/${useRoot ? '' : `${terraformDocsDocsDir}/`}${remoteFile}`
|
||||
)
|
||||
|
||||
let response = await fetchRevalidatePerDay(
|
||||
`https://raw.githubusercontent.com/${terraformDocsOrg}/${terraformDocsRepo}/${terraformDocsBranch}/${useRoot ? '' : `${terraformDocsDocsDir}/`}${remoteFile}`
|
||||
)
|
||||
|
||||
let rawContent = await response.text()
|
||||
// Strip out HTML comments
|
||||
rawContent = rawContent.replace(/<!--.*?-->/, '')
|
||||
let { content, data } = matter(rawContent)
|
||||
|
||||
// Remove the title from the content so it isn't duplicated in the final display
|
||||
content = removeRedundantH1(content)
|
||||
|
||||
Object.assign(meta, data)
|
||||
|
||||
if (!isValidGuideFrontmatter(meta)) {
|
||||
throw Error('Guide frontmatter is invalid.')
|
||||
}
|
||||
|
||||
return {
|
||||
pathname:
|
||||
`/guides/platform/terraform${slug?.length ? `/${slug.join('/')}` : ''}` satisfies `/${string}`,
|
||||
meta,
|
||||
content,
|
||||
editLink,
|
||||
}
|
||||
}
|
||||
|
||||
const generateStaticParams = async () => pageMap.map(({ slug }) => ({ slug: slug ? [slug] : [] }))
|
||||
const generateMetadata = genGuideMeta(getContent)
|
||||
|
||||
export default TerraformDocs
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -1,410 +0,0 @@
|
||||
import { codeBlock } from 'common-tags'
|
||||
import { Check, PlusCircle } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
|
||||
import {
|
||||
CodeBlock,
|
||||
Heading,
|
||||
PopoverContent_Shadcn_,
|
||||
PopoverTrigger_Shadcn_,
|
||||
Popover_Shadcn_,
|
||||
} from 'ui'
|
||||
|
||||
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import { fetchRevalidatePerDay } from '~/features/helpers.fetch'
|
||||
import { TabPanel, Tabs } from '~/features/ui/Tabs'
|
||||
import {
|
||||
terraformDocsBranch,
|
||||
terraformDocsDocsDir,
|
||||
terraformDocsOrg,
|
||||
terraformDocsRepo,
|
||||
} from '../terraformConstants'
|
||||
|
||||
const meta = {
|
||||
title: 'Terraform Provider reference',
|
||||
subtitle: 'Resources and data sources available through the Terraform Provider',
|
||||
}
|
||||
|
||||
const generateMetadata = genGuideMeta(() => ({
|
||||
pathname: '/guides/platform/terraform/reference',
|
||||
meta,
|
||||
}))
|
||||
|
||||
function ProviderSettings({ schema }: { schema: any }) {
|
||||
const attributes = schema.block.attributes
|
||||
|
||||
const example = codeBlock`
|
||||
provider "supabase" {
|
||||
${Object.keys(attributes).map(
|
||||
(attribute) =>
|
||||
`${attribute} = ${attributes[attribute].type === 'string' ? `""` : '<value>'}`
|
||||
)}
|
||||
}
|
||||
`
|
||||
|
||||
return (
|
||||
<section aria-labelledby="provider-settings" className="prose max-w-none">
|
||||
<Heading tag="h2">Provider settings</Heading>
|
||||
<p>
|
||||
Use these settings to configure your Supabase provider and authenticate to your Supabase
|
||||
project.
|
||||
</p>
|
||||
<Heading tag="h3">Example usage</Heading>
|
||||
<CodeBlock className="not-prose">{example}</CodeBlock>
|
||||
<Heading tag="h3">Details</Heading>
|
||||
{/* extra div because width restriction doesn't work on table itself */}
|
||||
<div className="w-full overflow-auto">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Attribute</th>
|
||||
<th>Description</th>
|
||||
<th>Type</th>
|
||||
<th>Optional</th>
|
||||
<th>Sensitive</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.keys(schema.block.attributes).map((attribute) => (
|
||||
<tr key={attribute}>
|
||||
<td>{attribute}</td>
|
||||
<td>
|
||||
<ReactMarkdown>{attributes[attribute].description}</ReactMarkdown>
|
||||
</td>
|
||||
<td>{attributes[attribute].type}</td>
|
||||
<td className="align-middle">
|
||||
{attributes[attribute].optional && (
|
||||
<>
|
||||
<Check className="ml-[2.5ch]" />
|
||||
<span className="sr-only">true</span>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<td className="align-middle">
|
||||
{attributes[attribute].sensitive && (
|
||||
<>
|
||||
<Check className="ml-[2.5ch]" />
|
||||
<span className="sr-only">true</span>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function Resources({ schema }: { schema: any }) {
|
||||
return (
|
||||
<section aria-labelledby="resources" className="prose max-w-none">
|
||||
<Heading tag="h2">Resources</Heading>
|
||||
<p>You can configure these resources using the Supabase Terraform provider:</p>
|
||||
<Tabs>
|
||||
{Object.keys(schema).map((resource) => (
|
||||
<TabPanel id={resource} label={resource}>
|
||||
<Heading tag="h4">Example usage</Heading>
|
||||
<CodeBlock className="not-prose">{codeBlock`
|
||||
resource "${resource}" "<label>" {
|
||||
${Object.keys(schema[resource].block.attributes)
|
||||
.filter((attribute) => !schema[resource].block.attributes[attribute].computed)
|
||||
.map(
|
||||
(attribute) =>
|
||||
`${attribute} = ${
|
||||
schema[resource].block.attributes[attribute].type === 'string'
|
||||
? `""`
|
||||
: '<value>'
|
||||
}`
|
||||
)}
|
||||
}
|
||||
`}</CodeBlock>
|
||||
<Heading tag="h4">Details</Heading>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Attribute</th>
|
||||
<th>Description</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Optional</th>
|
||||
<th>Read-only</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.keys(schema[resource].block.attributes).map((attribute) => (
|
||||
<tr key={attribute}>
|
||||
<td>
|
||||
<code>{attribute}</code>
|
||||
</td>
|
||||
<td>
|
||||
<ReactMarkdown>
|
||||
{schema[resource].block.attributes[attribute].description}
|
||||
</ReactMarkdown>
|
||||
</td>
|
||||
<td>
|
||||
{schema[resource].block.attributes[attribute].type ?? (
|
||||
<Popover_Shadcn_>
|
||||
<PopoverTrigger_Shadcn_ asChild>
|
||||
<button className="flex items-center justify-between gap-2">
|
||||
Nested type
|
||||
<PlusCircle size={14} className="shrink-0" />
|
||||
</button>
|
||||
</PopoverTrigger_Shadcn_>
|
||||
<PopoverContent_Shadcn_ className="max-h-[50vh] overflow-auto">
|
||||
<ul>
|
||||
{Object.keys(
|
||||
schema[resource].block.attributes[attribute].nested_type.attributes
|
||||
).map((nestedAttribute) => (
|
||||
<li key={nestedAttribute}>
|
||||
{nestedAttribute}
|
||||
<ul>
|
||||
<li>
|
||||
<ReactMarkdown className="*:!m-0">
|
||||
{
|
||||
schema[resource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].description
|
||||
}
|
||||
</ReactMarkdown>
|
||||
</li>
|
||||
<li>
|
||||
{schema[resource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].type ?? 'nested type'}
|
||||
</li>
|
||||
{schema[resource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].required && <li>Required</li>}
|
||||
{schema[resource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].optional && <li>Optional</li>}
|
||||
{schema[resource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].computed && <li>Read-only</li>}
|
||||
</ul>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</PopoverContent_Shadcn_>
|
||||
</Popover_Shadcn_>
|
||||
)}
|
||||
</td>
|
||||
<td className="align-middle">
|
||||
{schema[resource].block.attributes[attribute].required && (
|
||||
<>
|
||||
<Check className="ml-[2.5ch]" />
|
||||
<span className="sr-only">true</span>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<td className="align-middle">
|
||||
{schema[resource].block.attributes[attribute].optional && (
|
||||
<>
|
||||
<Check className="ml-[2.5ch]" />
|
||||
<span className="sr-only">true</span>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<td className="align-middle">
|
||||
{schema[resource].block.attributes[attribute].computed && (
|
||||
<>
|
||||
<Check className="ml-[2.5ch]" />
|
||||
<span className="sr-only">true</span>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</TabPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function DataSources({ schema }: { schema: any }) {
|
||||
return (
|
||||
<section aria-labelledby="data-sources" className="prose max-w-none">
|
||||
<Heading tag="h2">Data sources</Heading>
|
||||
<p>You can read these resources using the Supabase Terraform provider:</p>
|
||||
<Tabs>
|
||||
{Object.keys(schema).map((dataSource) => (
|
||||
<TabPanel id={dataSource} label={dataSource}>
|
||||
<Heading tag="h4">Example usage</Heading>
|
||||
<CodeBlock className="not-prose">{codeBlock`
|
||||
resource "${dataSource}" "all" {
|
||||
${Object.keys(schema[dataSource].block.attributes)
|
||||
.filter(
|
||||
(attribute) => !schema[dataSource].block.attributes[attribute].computed
|
||||
)
|
||||
.map(
|
||||
(attribute) =>
|
||||
`${attribute} = ${
|
||||
schema[dataSource].block.attributes[attribute].type === 'string'
|
||||
? `""`
|
||||
: '<value>'
|
||||
}`
|
||||
)}
|
||||
}
|
||||
`}</CodeBlock>
|
||||
<Heading tag="h4">Details</Heading>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Attribute</th>
|
||||
<th>Description</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Optional</th>
|
||||
<th>Read-only</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.keys(schema[dataSource].block.attributes).map((attribute) => (
|
||||
<tr key={attribute}>
|
||||
<td>
|
||||
<code>{attribute}</code>
|
||||
</td>
|
||||
<td>
|
||||
<ReactMarkdown>
|
||||
{schema[dataSource].block.attributes[attribute].description}
|
||||
</ReactMarkdown>
|
||||
</td>
|
||||
<td>
|
||||
{schema[dataSource].block.attributes[attribute].type ?? (
|
||||
<Popover_Shadcn_>
|
||||
<PopoverTrigger_Shadcn_ asChild>
|
||||
<button className="flex items-center justify-between gap-2">
|
||||
Nested type
|
||||
<PlusCircle size={14} />
|
||||
</button>
|
||||
</PopoverTrigger_Shadcn_>
|
||||
<PopoverContent_Shadcn_ className="max-h-[50vh] overflow-auto">
|
||||
{schema[dataSource].block.attributes[attribute].nested_type
|
||||
.nesting_mode === 'set' && 'Array of:'}
|
||||
<ul>
|
||||
{Object.keys(
|
||||
schema[dataSource].block.attributes[attribute].nested_type
|
||||
.attributes
|
||||
).map((nestedAttribute) => (
|
||||
<li key={nestedAttribute}>
|
||||
{nestedAttribute}
|
||||
<ul>
|
||||
<li>
|
||||
<ReactMarkdown className="*:!m-0">
|
||||
{
|
||||
schema[dataSource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].description
|
||||
}
|
||||
</ReactMarkdown>
|
||||
</li>
|
||||
<li>
|
||||
{schema[dataSource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].type ?? 'nested type'}
|
||||
</li>
|
||||
{schema[dataSource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].required && <li>Required</li>}
|
||||
{schema[dataSource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].optional && <li>Optional</li>}
|
||||
{schema[dataSource].block.attributes[attribute].nested_type
|
||||
.attributes[nestedAttribute].computed && <li>Read-only</li>}
|
||||
</ul>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</PopoverContent_Shadcn_>
|
||||
</Popover_Shadcn_>
|
||||
)}
|
||||
</td>
|
||||
<td className="align-middle">
|
||||
{schema[dataSource].block.attributes[attribute].required && (
|
||||
<>
|
||||
<Check className="ml-[2.5ch]" />
|
||||
<span className="sr-only">true</span>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<td className="align-middle">
|
||||
{schema[dataSource].block.attributes[attribute].optional && (
|
||||
<>
|
||||
<Check className="ml-[2.5ch]" />
|
||||
<span className="sr-only">true</span>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<td className="align-middle">
|
||||
{schema[dataSource].block.attributes[attribute].computed && (
|
||||
<>
|
||||
<Check className="ml-[2.5ch]" />
|
||||
<span className="sr-only">true</span>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</TabPanel>
|
||||
))}
|
||||
</Tabs>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
const TerraformReferencePage = async () => {
|
||||
const { schema } = await getSchema()
|
||||
|
||||
const editLink = newEditLink('supabase/terraform-provider-supabase')
|
||||
|
||||
return (
|
||||
<GuideTemplate meta={meta} editLink={editLink}>
|
||||
The Terraform Provider provices access to{' '}
|
||||
<Link
|
||||
href="https://developer.hashicorp.com/terraform/language/resources"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
resources
|
||||
</Link>{' '}
|
||||
and{' '}
|
||||
<Link
|
||||
href="https://developer.hashicorp.com/terraform/language/data-sources"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
data sources
|
||||
</Link>
|
||||
. Resources are infrastructure objects, such as a Supabase project, that you can declaratively
|
||||
configure. Data sources are sources of information about your Supabase instances.
|
||||
<ProviderSettings
|
||||
schema={schema.provider_schemas['registry.terraform.io/supabase/supabase'].provider}
|
||||
/>
|
||||
<Resources
|
||||
schema={schema.provider_schemas['registry.terraform.io/supabase/supabase'].resource_schemas}
|
||||
/>
|
||||
<DataSources
|
||||
schema={
|
||||
schema.provider_schemas['registry.terraform.io/supabase/supabase'].data_source_schemas
|
||||
}
|
||||
/>
|
||||
</GuideTemplate>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch JSON schema from external repo
|
||||
*/
|
||||
const getSchema = async () => {
|
||||
let response = await fetchRevalidatePerDay(
|
||||
`https://raw.githubusercontent.com/${terraformDocsOrg}/${terraformDocsRepo}/${terraformDocsBranch}/${terraformDocsDocsDir}/schema.json`
|
||||
)
|
||||
if (!response.ok) throw Error('Failed to fetch Terraform JSON schema from GitHub')
|
||||
|
||||
const schema = await response.json()
|
||||
|
||||
return {
|
||||
schema,
|
||||
}
|
||||
}
|
||||
|
||||
export default TerraformReferencePage
|
||||
export { generateMetadata }
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* Information on where to fetch docs content
|
||||
*/
|
||||
const terraformDocsOrg = 'supabase'
|
||||
const terraformDocsRepo = 'terraform-provider-supabase'
|
||||
const terraformDocsBranch = 'v1.1.3'
|
||||
const terraformDocsDocsDir = 'docs'
|
||||
|
||||
export { terraformDocsOrg, terraformDocsRepo, terraformDocsBranch, terraformDocsDocsDir }
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const AiGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['ai', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('ai')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['ai', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default AiGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function AiLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
+2
@@ -10,6 +10,8 @@ import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLin
|
||||
import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
|
||||
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
// We fetch these docs at build time from an external repo
|
||||
const org = 'supabase'
|
||||
const repo = 'vecs'
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const ApiGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['api', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('api')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['api', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default ApiGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function ApiLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const AuthGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['auth', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('auth')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['auth', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default AuthGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function AuthLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const CronGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['cron', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('cron')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['cron', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default CronGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function CronLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const DatabaseGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['database', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('database')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['database', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default DatabaseGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
File renamed without changes.
+2
@@ -18,6 +18,8 @@ import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
|
||||
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
|
||||
import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
// We fetch these docs at build time from an external repo
|
||||
const org = 'supabase'
|
||||
const repo = 'wrappers'
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function DatabaseLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const DeploymentGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['deployment', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('deployment')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['deployment', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default DeploymentGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
+2
@@ -11,6 +11,8 @@ import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
|
||||
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
|
||||
import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
// We fetch these docs at build time from an external repo
|
||||
const org = 'supabase'
|
||||
const repo = 'setup-cli'
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function DeploymentLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
+2
@@ -18,6 +18,8 @@ import {
|
||||
terraformDocsRepo,
|
||||
} from '../terraformConstants'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
// Each external docs page is mapped to a local page
|
||||
const pageMap = [
|
||||
{
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const FunctionsGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['functions', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('functions')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['functions', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default FunctionsGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function FunctionsLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const GettingStartedGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['getting-started', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('getting-started')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['getting-started', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default GettingStartedGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -23,7 +23,7 @@ ${content}
|
||||
|
||||
return (
|
||||
<GuideTemplate
|
||||
meta={{ title: `API Prompt: ${heading}` }}
|
||||
meta={{ title: `AI Prompt: ${heading}` }}
|
||||
content={content}
|
||||
editLink={newEditLink(`supabase/supabase/blob/master/examples/prompts/${slug}.md`)}
|
||||
/>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { type NavMenuSection } from '~/components/Navigation/Navigation.types'
|
||||
import Layout from '~/layouts/guides'
|
||||
import { getAiPrompts } from '../getting-started/ai-prompts/[slug]/AiPrompts.utils'
|
||||
|
||||
export default async function GettingStartedLayout({ children }: { children: React.ReactNode }) {
|
||||
const additionalNavItems = { prompts: await getPrompts() }
|
||||
|
||||
return <Layout additionalNavItems={additionalNavItems}>{children}</Layout>
|
||||
}
|
||||
|
||||
async function getPrompts() {
|
||||
const prompts = await getAiPrompts()
|
||||
return prompts.map(
|
||||
(prompt) =>
|
||||
({
|
||||
name: prompt.heading,
|
||||
url: `/guides/getting-started/ai-prompts/${prompt.filename}`,
|
||||
}) as Partial<NavMenuSection>
|
||||
)
|
||||
}
|
||||
+4
-2
@@ -5,12 +5,14 @@ import rehypeSlug from 'rehype-slug'
|
||||
|
||||
import { genGuideMeta } from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
|
||||
import { fetchRevalidatePerDay } from '~/features/helpers.fetch'
|
||||
import { fetchRevalidatePerDay_TEMP_TESTING } from '~/features/helpers.fetch'
|
||||
import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform'
|
||||
import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
|
||||
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
|
||||
import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
// We fetch these docs at build time from an external repo
|
||||
const org = 'supabase'
|
||||
const repo = 'pg_graphql'
|
||||
@@ -133,7 +135,7 @@ const getContent = async ({ slug }: Params) => {
|
||||
|
||||
const editLink = newEditLink(`${org}/${repo}/blob/${branch}/${docsDir}/${remoteFile}`)
|
||||
|
||||
const response = await fetchRevalidatePerDay(
|
||||
const response = await fetchRevalidatePerDay_TEMP_TESTING(
|
||||
`https://raw.githubusercontent.com/${org}/${repo}/${branch}/${docsDir}/${remoteFile}`
|
||||
)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function GraphQlLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const IntegrationsGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['integrations', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('integrations')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['integrations', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default IntegrationsGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,39 @@
|
||||
import { IS_PLATFORM } from 'common'
|
||||
import { unstable_cache } from 'next/cache'
|
||||
|
||||
import { type NavMenuSection } from '~/components/Navigation/Navigation.types'
|
||||
import Layout from '~/layouts/guides'
|
||||
import { supabaseMisc } from '~/lib/supabaseMisc'
|
||||
|
||||
export default async function IntegrationsLayout({ children }: { children: React.ReactNode }) {
|
||||
const additionalNavItems = { integrations: await getPartners() }
|
||||
|
||||
return <Layout additionalNavItems={additionalNavItems}>{children}</Layout>
|
||||
}
|
||||
|
||||
// Will need to turn on revalidation later, just turning it off for now so we
|
||||
// can slowly turn things back on while monitoring usage
|
||||
const getPartners = unstable_cache(getPartnersImpl, [], { revalidate: false })
|
||||
async function getPartnersImpl() {
|
||||
if (!IS_PLATFORM) return []
|
||||
|
||||
const { data, error } = await supabaseMisc()
|
||||
.from('partners')
|
||||
.select('slug, title')
|
||||
.eq('approved', true)
|
||||
.eq('type', 'technology')
|
||||
.order('title')
|
||||
if (error) {
|
||||
console.error(new Error('Error fetching partners', { cause: error }))
|
||||
}
|
||||
|
||||
const partnerNavItems = (data ?? []).map(
|
||||
(partner) =>
|
||||
({
|
||||
name: partner.title,
|
||||
url: `https://supabase.com/partners/integrations/${partner.slug}` as `https://${string}`,
|
||||
}) as Partial<NavMenuSection>
|
||||
)
|
||||
|
||||
return partnerNavItems
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const LocalDevelopmentGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['local-development', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('local-development')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['local-development', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default LocalDevelopmentGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
File renamed without changes.
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function LocalDevelopmentLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const PlatformGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['platform', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('platform')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['platform', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default PlatformGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function PlatformLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const QueuesGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['queues', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('queues')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['queues', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default QueuesGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function QueuesLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const RealtimeGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['realtime', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('realtime')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['realtime', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default RealtimeGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function RealtimeLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const ResourcesGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['resources', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('resources')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['resources', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default ResourcesGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function ResourcesLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const SelfHostingGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['self-hosting', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('self-hosting')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['self-hosting', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default SelfHostingGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function SelfHostingLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const StorageGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['storage', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('storage')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['storage', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default StorageGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function StorageLayout({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
getGuidesMarkdown,
|
||||
genGuideMeta,
|
||||
genGuidesStaticParams,
|
||||
} from '~/features/docs/GuidesMdx.utils'
|
||||
import { GuideTemplate } from '~/features/docs/GuidesMdx.template'
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
type Params = { slug?: string[] }
|
||||
|
||||
const MonitoringTroubleshootingGuidePage = async ({ params }: { params: Params }) => {
|
||||
const slug = ['telemetry', ...(params.slug ?? [])]
|
||||
const data = await getGuidesMarkdown(slug)
|
||||
|
||||
return <GuideTemplate {...data!} />
|
||||
}
|
||||
|
||||
const generateStaticParams = genGuidesStaticParams('telemetry')
|
||||
const generateMetadata = genGuideMeta((params: { slug?: string[] }) =>
|
||||
getGuidesMarkdown(['telemetry', ...(params.slug ?? [])])
|
||||
)
|
||||
|
||||
export default MonitoringTroubleshootingGuidePage
|
||||
export { generateStaticParams, generateMetadata }
|
||||
@@ -0,0 +1,5 @@
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
export default async function Telemetry({ children }: { children: React.ReactNode }) {
|
||||
return <Layout>{children}</Layout>
|
||||
}
|
||||
@@ -41,11 +41,14 @@ export default async function GlobalTroubleshootingPage() {
|
||||
/>
|
||||
<TroubleshootingListController />
|
||||
<TroubleshootingFilterEmptyState />
|
||||
<div id={TROUBLESHOOTING_CONTAINER_ID}>
|
||||
<div id={TROUBLESHOOTING_CONTAINER_ID} className="@container/troubleshooting">
|
||||
<h2 className="sr-only">Matching troubleshooting entries</h2>
|
||||
<ul>
|
||||
<ul className="grid @4xl/troubleshooting:grid-cols-[78%_15%_7%]">
|
||||
{troubleshootingEntries.map((entry) => (
|
||||
<li key={entry.data.database_id}>
|
||||
<li
|
||||
key={entry.data.database_id}
|
||||
className="grid grid-cols-subgrid @4xl/troubleshooting:col-span-3"
|
||||
>
|
||||
<TroubleshootingPreview entry={entry} />
|
||||
</li>
|
||||
))}
|
||||
|
||||
@@ -5,12 +5,20 @@ const CostWarning = () => (
|
||||
<Admonition type="warning">
|
||||
<p>
|
||||
To keep SMS sending costs under control, make sure you adjust your project's rate limits
|
||||
and <Link href="/guides/auth/auth-captcha">configure CAPTCHA</Link>. See the{' '}
|
||||
<Link href="/guides/platform/going-into-prod">Production Checklist</Link> to learn more.
|
||||
and{' '}
|
||||
<Link href="/guides/auth/auth-captcha">
|
||||
<u>configure CAPTCHA</u>
|
||||
</Link>
|
||||
. See the{' '}
|
||||
<Link href="/guides/platform/going-into-prod">
|
||||
<u>Production Checklist</u>
|
||||
</Link>{' '}
|
||||
to learn more.
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
Some countries have special regulations for services that send SMS messages to users, for
|
||||
example, India's TRAI DLT regulations. Remember to look up and follow the regulations of
|
||||
Some countries have special regulations for services that send SMS messages to users, (e.g
|
||||
India's TRAI DLT regulations). Remember to look up and follow the regulations of
|
||||
countries where you operate.
|
||||
</p>
|
||||
</Admonition>
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from 'react'
|
||||
|
||||
import { type Database, useConstant, useIsLoggedIn } from 'common'
|
||||
import { TelemetryActions } from 'common/telemetry-constants'
|
||||
import { Button, cn } from 'ui'
|
||||
|
||||
import { IS_PLATFORM } from '~/lib/constants'
|
||||
@@ -108,9 +109,8 @@ function Feedback({ className }: { className?: string }) {
|
||||
|
||||
function handleVote(response: Response) {
|
||||
sendTelemetryEvent({
|
||||
category: 'docs',
|
||||
action: 'feedback_voted',
|
||||
label: response,
|
||||
action: TelemetryActions.DOCS_FEEDBACK_CLICKED,
|
||||
properties: { response },
|
||||
})
|
||||
sendFeedbackVote(response)
|
||||
dispatch({ event: 'VOTED', response })
|
||||
|
||||
@@ -128,8 +128,6 @@ const GuidesTableOfContents = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [hash, JSON.stringify(displayedList)])
|
||||
|
||||
if (!displayedList.length) return
|
||||
|
||||
const tocVideoPreview = `https://img.youtube.com/vi/${video}/0.jpg`
|
||||
|
||||
return (
|
||||
@@ -140,29 +138,33 @@ const GuidesTableOfContents = ({
|
||||
</div>
|
||||
)}
|
||||
<Feedback key={pathname} />
|
||||
<span className="block font-mono text-xs uppercase text-foreground px-5 mb-6">
|
||||
On this page
|
||||
</span>
|
||||
<ul className="toc-menu list-none pl-5 text-[0.8rem] grid gap-2">
|
||||
{displayedList.map((item, i) => (
|
||||
<li key={`${item.level}-${i}`} className={item.level === 3 ? 'ml-4' : ''}>
|
||||
<a
|
||||
href={`#${formatSlug(item.link)}`}
|
||||
className="text-foreground-lighter hover:text-brand-link transition-colors"
|
||||
>
|
||||
{formatTOCHeader(removeAnchor(item.text)).map((x, index) => (
|
||||
<Fragment key={index}>
|
||||
{x.type === 'code' ? (
|
||||
<code className="text-xs border rounded bg-muted">{x.value}</code>
|
||||
) : (
|
||||
x.value
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{displayedList.length > 0 && (
|
||||
<>
|
||||
<span className="block font-mono text-xs uppercase text-foreground px-5 mb-6">
|
||||
On this page
|
||||
</span>
|
||||
<ul className="toc-menu list-none pl-5 text-[0.8rem] grid gap-2">
|
||||
{displayedList.map((item, i) => (
|
||||
<li key={`${item.level}-${i}`} className={item.level === 3 ? 'ml-4' : ''}>
|
||||
<a
|
||||
href={`#${formatSlug(item.link)}`}
|
||||
className="text-foreground-lighter hover:text-brand-link transition-colors"
|
||||
>
|
||||
{formatTOCHeader(removeAnchor(item.text)).map((x, index) => (
|
||||
<Fragment key={index}>
|
||||
{x.type === 'code' ? (
|
||||
<code className="text-xs border rounded bg-muted">{x.value}</code>
|
||||
) : (
|
||||
x.value
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -179,7 +179,6 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const headers = new Headers()
|
||||
|
||||
if (code) {
|
||||
const cookies = parse(request.headers.get('Cookie') ?? '')
|
||||
const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {
|
||||
cookies: {
|
||||
getAll() {
|
||||
|
||||
@@ -4,7 +4,7 @@ Go to [database.new](https://database.new) and create a new Supabase project.
|
||||
|
||||
When your project is up and running, go to the [Table Editor](https://supabase.com/dashboard/project/_/editor), create a new table and insert some data.
|
||||
|
||||
Alternatively, you can run the following snippet in your project's [SQL Editor](https://supabase.com/dashboard/project/_/sql/new). This will create a `countries` table with some sample data.
|
||||
Alternatively, you can run the following snippet in your project's [SQL Editor](https://supabase.com/dashboard/project/_/sql/new). This will create a `instruments` table with some sample data.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
@@ -12,18 +12,18 @@ Alternatively, you can run the following snippet in your project's [SQL Editor](
|
||||
|
||||
```sql SQL_EDITOR
|
||||
-- Create the table
|
||||
create table countries (
|
||||
create table instruments (
|
||||
id bigint primary key generated always as identity,
|
||||
name text not null
|
||||
);
|
||||
-- Insert some sample data into the table
|
||||
insert into countries (name)
|
||||
insert into instruments (name)
|
||||
values
|
||||
('Canada'),
|
||||
('United States'),
|
||||
('Mexico');
|
||||
('violin'),
|
||||
('viola'),
|
||||
('cello');
|
||||
|
||||
alter table countries enable row level security;
|
||||
alter table instruments enable row level security;
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
@@ -37,8 +37,8 @@ Make the data in your table publicly readable by adding an RLS policy:
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```sql SQL_EDITOR
|
||||
create policy "public can read countries"
|
||||
on public.countries
|
||||
create policy "public can read instruments"
|
||||
on public.instruments
|
||||
for select to anon
|
||||
using (true);
|
||||
```
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Clock, Heart, Server, SquareStack } from 'lucide-react'
|
||||
import { Clock, Heart, Server, SquareStack, Telescope } from 'lucide-react'
|
||||
|
||||
import {
|
||||
IconBranching,
|
||||
@@ -87,6 +87,8 @@ function getMenuIcon(menuKey: string, width: number = 16, height: number = 16, c
|
||||
return <IconGitHub width={width} height={height} className={className} />
|
||||
case 'support':
|
||||
return <IconSupport width={width} height={height} className={className} />
|
||||
case 'telemetry':
|
||||
return <Telescope width={width} height={height} className={className} />
|
||||
case 'troubleshooting':
|
||||
return <IconTroubleshooting width={width} height={height} className={className} />
|
||||
case 'contributing':
|
||||
|
||||
@@ -119,10 +119,15 @@ export const GLOBAL_MENU_ITEMS: GlobalMenuItems = [
|
||||
level: 'platform',
|
||||
},
|
||||
{
|
||||
label: 'Monitoring & Troubleshooting',
|
||||
label: 'Telemetry',
|
||||
icon: 'telemetry',
|
||||
href: '/guides/telemetry',
|
||||
level: 'telemetry',
|
||||
},
|
||||
{
|
||||
label: 'Troubleshooting',
|
||||
icon: 'troubleshooting',
|
||||
href: '/guides/monitoring-troubleshooting',
|
||||
level: 'troubleshooting',
|
||||
href: '/guides/troubleshooting',
|
||||
},
|
||||
],
|
||||
],
|
||||
@@ -212,11 +217,6 @@ export const GLOBAL_MENU_ITEMS: GlobalMenuItems = [
|
||||
label: 'Resources',
|
||||
menuItems: [
|
||||
[
|
||||
/* {
|
||||
label: 'Troubleshooting',
|
||||
icon: 'contributing',
|
||||
href: '/guides/troubleshooting',
|
||||
}, */
|
||||
{
|
||||
label: 'Glossary',
|
||||
icon: 'resources',
|
||||
@@ -1322,7 +1322,7 @@ export const functions: NavMenuConstant = {
|
||||
name: 'Guides',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Managing dependencies', url: '/guides/functions/import-maps' },
|
||||
{ name: 'Managing dependencies', url: '/guides/functions/dependencies' },
|
||||
{
|
||||
name: 'Managing environment variables',
|
||||
url: '/guides/functions/secrets',
|
||||
@@ -1359,6 +1359,10 @@ export const functions: NavMenuConstant = {
|
||||
name: 'Running AI Models',
|
||||
url: '/guides/functions/ai-models',
|
||||
},
|
||||
{
|
||||
name: 'Wasm modules',
|
||||
url: '/guides/functions/wasm',
|
||||
},
|
||||
{
|
||||
name: 'Deploying with CI / CD pipelines',
|
||||
url: '/guides/functions/cicd-workflow',
|
||||
@@ -2091,71 +2095,35 @@ export const platform: NavMenuConstant = {
|
||||
],
|
||||
}
|
||||
|
||||
export const monitoring_troubleshooting: NavMenuConstant = {
|
||||
icon: 'troubleshooting',
|
||||
title: 'Monitor & Fix',
|
||||
url: '/guides/monitoring-troubleshooting',
|
||||
export const telemetry: NavMenuConstant = {
|
||||
icon: 'telemetry',
|
||||
title: 'Telemetry',
|
||||
url: '/guides/telemetry',
|
||||
items: [
|
||||
{ name: 'Overview', url: '/guides/monitoring-troubleshooting' },
|
||||
{ name: 'Overview', url: '/guides/telemetry' },
|
||||
{
|
||||
name: 'Logging & observability',
|
||||
url: undefined,
|
||||
items: [
|
||||
{
|
||||
name: 'Logging',
|
||||
url: '/guides/monitoring-troubleshooting/logs',
|
||||
url: '/guides/telemetry/logs',
|
||||
},
|
||||
{
|
||||
name: 'Advanced log filtering',
|
||||
url: '/guides/monitoring-troubleshooting/advanced-log-filtering',
|
||||
url: '/guides/telemetry/advanced-log-filtering',
|
||||
},
|
||||
{
|
||||
name: 'Log drains',
|
||||
url: '/guides/monitoring-troubleshooting/log-drains',
|
||||
url: '/guides/telemetry/log-drains',
|
||||
},
|
||||
{
|
||||
name: 'Metrics',
|
||||
url: '/guides/monitoring-troubleshooting/metrics',
|
||||
url: '/guides/telemetry/metrics',
|
||||
},
|
||||
{
|
||||
name: 'Sentry integration',
|
||||
url: '/guides/monitoring-troubleshooting/sentry-monitoring',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Troubleshooting',
|
||||
url: undefined,
|
||||
items: [
|
||||
{
|
||||
name: 'HTTP and project issues',
|
||||
url: '/guides/monitoring-troubleshooting/troubleshooting',
|
||||
},
|
||||
{
|
||||
name: 'High disk IO consumption',
|
||||
url: '/guides/monitoring-troubleshooting/exhaust-disk-io',
|
||||
},
|
||||
{
|
||||
name: 'High CPU usage',
|
||||
url: '/guides/monitoring-troubleshooting/exhaust-cpu',
|
||||
},
|
||||
{
|
||||
name: 'High RAM usage',
|
||||
url: '/guides/monitoring-troubleshooting/exhaust-ram',
|
||||
},
|
||||
{
|
||||
name: 'High swap usage',
|
||||
url: '/guides/monitoring-troubleshooting/exhaust-swap',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Status codes',
|
||||
url: undefined,
|
||||
items: [
|
||||
{
|
||||
name: 'HTTP status codes',
|
||||
url: '/guides/monitoring-troubleshooting/http-status-codes',
|
||||
url: '/guides/telemetry/sentry-monitoring',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@ enum MenuId {
|
||||
Queues = 'queues',
|
||||
Platform = 'platform',
|
||||
Deployment = 'deployment',
|
||||
MonitoringTroubleshooting = 'monitoring_troubleshooting',
|
||||
Telemetry = 'telemetry',
|
||||
Resources = 'resources',
|
||||
SelfHosting = 'self_hosting',
|
||||
Integrations = 'integrations',
|
||||
@@ -92,7 +92,7 @@ const menus: Menu[] = [
|
||||
type: 'guide',
|
||||
},
|
||||
{
|
||||
id: MenuId.MonitoringTroubleshooting,
|
||||
id: MenuId.Telemetry,
|
||||
type: 'guide',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -125,8 +125,8 @@ export const getMenuId = (pathname: string | null) => {
|
||||
return MenuId.Integrations
|
||||
case pathname.startsWith('local-development'):
|
||||
return MenuId.LocalDevelopment
|
||||
case pathname.startsWith('monitoring-troubleshooting'):
|
||||
return MenuId.MonitoringTroubleshooting
|
||||
case pathname.startsWith('telemetry'):
|
||||
return MenuId.Telemetry
|
||||
case pathname.startsWith('platform'):
|
||||
return MenuId.Platform
|
||||
case pathname.startsWith('queues'):
|
||||
|
||||
@@ -78,12 +78,20 @@ final data = await supabase.schema('myschema').from('todos').select();
|
||||
|
||||
```bash
|
||||
# Append /rest/v1/ to your URL, and then use the table name as the route.
|
||||
# Use Accept-Profile header for GET and HEAD.
|
||||
# Use Content-Profile header for POST, PATCH, PUT and DELETE.
|
||||
|
||||
# for GET or HEAD request use Accept-Profile
|
||||
curl '<SUPABASE_URL>/rest/v1/todos' \
|
||||
-H "apikey: <SUPABASE_ANON_KEY>" \
|
||||
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
|
||||
-H "Accept-Profile: myschema"
|
||||
|
||||
# for POST, PATCH, PUT and DELETE Request use Content-Profile
|
||||
curl -X POST '<SUPABASE_URL>/rest/v1/todos' \
|
||||
-H "apikey: <SUPABASE_ANON_KEY>" \
|
||||
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Content-Profile: myschema" \
|
||||
-d '{"column_name": "value"}'
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
@@ -133,7 +133,7 @@ If you're using PKCE flow, edit the Magic Link [email template](/docs/guides/aut
|
||||
<h2>Magic Link</h2>
|
||||
|
||||
<p>Follow this link to login:</p>
|
||||
<p><a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=magiclink">Log In</a></p>
|
||||
<p><a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email">Log In</a></p>
|
||||
```
|
||||
|
||||
At the `/auth/confirm` endpoint, exchange the hash for the session:
|
||||
|
||||
@@ -18,7 +18,7 @@ The templating system provides the following variables for use:
|
||||
|
||||
| Name | Description |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `{{ .ConfirmationURL }}` | Contains the confirmation URL. For example, a signup confirmation URL would look like: `https://project-ref.supabase.co/auth/v1/verify?token={{ .TokenHash }}&type=signup&redirect_to=https://example.com/path` . |
|
||||
| `{{ .ConfirmationURL }}` | Contains the confirmation URL. For example, a signup confirmation URL would look like: `https://project-ref.supabase.co/auth/v1/verify?token={{ .TokenHash }}&type=email&redirect_to=https://example.com/path` . |
|
||||
| `{{ .Token }}` | Contains a 6-digit One-Time-Password (OTP) that can be used instead of the `{{. ConfirmationURL }}` . |
|
||||
| `{{ .TokenHash }}` | Contains a hashed version of the `{{ .Token }}`. This is useful for constructing your own email link in the email template. |
|
||||
| `{{ .SiteURL }}` | Contains your application's Site URL. This can be configured in your project's [authentication settings](/dashboard/project/_/auth/url-configuration). |
|
||||
|
||||
@@ -10,8 +10,8 @@ Claims returned must conform to our specification. Supabase Auth will check for
|
||||
|
||||
These are the fields currently available on an access token:
|
||||
|
||||
Required Claims: `aud`, `exp`, `iat`, `sub`, `email`, `phone`, `role`, `aal`, `session_id`
|
||||
Optional Claims: `jti`, `iss`, `nbf`, `app_metadata`, `user_metadata`, `amr`
|
||||
Required Claims: `iss`, `aud`, `exp`, `iat`, `sub`, `role`, `aal`, `session_id`
|
||||
Optional Claims: `jti`, `nbf`, `app_metadata`, `user_metadata`, `amr`, `email`, `phone`
|
||||
|
||||
**Inputs**
|
||||
|
||||
@@ -205,8 +205,57 @@ Return these only if your hook processed the input without errors.
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="add-admin-role"
|
||||
defaultActiveId="minimal-jwt"
|
||||
>
|
||||
<TabPanel id="minimal-jwt" label="Minimal JWT">
|
||||
|
||||
Sometimes the size of the JWT can be a problem especially if you're using a [Server-Side Rendering framework](/docs/guides/auth/server-side). Common situations where the JWT can get too large include:
|
||||
|
||||
- The user has a particularly large name, email address or phone number
|
||||
- The default JWT has too many claims coming from OAuth providers
|
||||
- A large avatar URL is included
|
||||
|
||||
To lower the size of the JWT you can define a Custom Access Token hook like the one below which will instruct the Auth server to issue a JWT with only the listed claims. Check the documentation above on what JWT claims must be present and cannot be removed.
|
||||
|
||||
Refer to the [Postgres JSON functions](https://www.postgresql.org/docs/current/functions-json.html) on how to manipulate `jsonb` objects.
|
||||
|
||||
```sql
|
||||
create or replace function public.custom_access_token_hook(event jsonb)
|
||||
returns jsonb
|
||||
language plpgsql
|
||||
as $$
|
||||
declare
|
||||
original_claims jsonb;
|
||||
new_claims jsonb;
|
||||
claim text;
|
||||
begin
|
||||
original_claims = event->'claims';
|
||||
new_claims = '{}'::jsonb;
|
||||
|
||||
foreach claim in array array[
|
||||
-- add claims you want to keep here
|
||||
'iss',
|
||||
'aud',
|
||||
'exp',
|
||||
'iat',
|
||||
'sub',
|
||||
'role',
|
||||
'aal',
|
||||
'session_id'
|
||||
] loop
|
||||
if original_claims ? claim then
|
||||
-- original_claims contains one of the listed claims, set it on new_claims
|
||||
new_claims = jsonb_set(new_claims, array[claim], original_claims->claim);
|
||||
end if;
|
||||
end loop;
|
||||
|
||||
return jsonb_build_object('claims', new_claims);
|
||||
end
|
||||
$$;
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="add-admin-role" label="Add admin role">
|
||||
|
||||
You can allow registered admin users to perform restricted actions by granting an `admin` claim to their token.
|
||||
|
||||
@@ -3,7 +3,13 @@ title: 'Phone Login'
|
||||
description: 'Learn about logging in to your platform using SMS one-time passwords.'
|
||||
---
|
||||
|
||||
Phone Login is a method of authentication that allows users to log in to a website or application without using a password. The user authenticates through a one-time code sent via SMS.
|
||||
Phone Login is a method of authentication that allows users to log in to a website or application without using a password. The user authenticates through a one-time password (OTP) sent via a channel (SMS or WhatsApp).
|
||||
|
||||
<Admonition type="info">
|
||||
|
||||
At this time, `WhatsApp` is only supported as a channel for the Twilio and Twilio Verify Providers.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Users can also log in with their phones using Native Mobile Login with the built-in identity provider. For Native Mobile Login with Android and iOS, see the [Social Login guides](/docs/guides/auth/social-login).
|
||||
|
||||
|
||||
@@ -86,10 +86,10 @@ For example, change the following:
|
||||
|
||||
```html
|
||||
<!-- Old -->
|
||||
<a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=signup">Confirm your mail</a>
|
||||
<a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email">Confirm your mail</a>
|
||||
|
||||
<!-- New -->
|
||||
<a href="{{ .RedirectTo }}/auth/confirm?token_hash={{ .TokenHash }}&type=signup"
|
||||
<a href="{{ .RedirectTo }}/auth/confirm?token_hash={{ .TokenHash }}&type=email"
|
||||
>Confirm your mail</a
|
||||
>
|
||||
```
|
||||
|
||||
@@ -401,7 +401,7 @@ If you have email confirmation turned on (the default), a new user will receive
|
||||
|
||||
Change the email template to support a server-side authentication flow.
|
||||
|
||||
Go to the [Auth templates](https://supabase.com/dashboard/project/_/auth/templates) page in your dashboard. In the `Confirm signup` template, change `{{ .ConfirmationURL }}` to `{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=signup`.
|
||||
Go to the [Auth templates](https://supabase.com/dashboard/project/_/auth/templates) page in your dashboard. In the `Confirm signup` template, change `{{ .ConfirmationURL }}` to `{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
@@ -791,7 +791,7 @@ If you have email confirmation turned on (the default), a new user will receive
|
||||
|
||||
Change the email template to support a server-side authentication flow.
|
||||
|
||||
Go to the [Auth templates](https://supabase.com/dashboard/project/_/auth/templates) page in your dashboard. In the `Confirm signup` template, change `{{ .ConfirmationURL }}` to `{{ .SiteURL }}/api/auth/confirm?token_hash={{ .TokenHash }}&type=signup`.
|
||||
Go to the [Auth templates](https://supabase.com/dashboard/project/_/auth/templates) page in your dashboard. In the `Confirm signup` template, change `{{ .ConfirmationURL }}` to `{{ .SiteURL }}/api/auth/confirm?token_hash={{ .TokenHash }}&type=email`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
@@ -928,9 +928,9 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
|
||||
You can also fetch static data at build time using Supabase. Note that there's no session or user at build time, so the data will be the same for everyone who sees the page.
|
||||
|
||||
Add some countries data to your database by running the [Countries Quickstart](https://supabase.com/dashboard/project/_/sql/quickstarts) in the dashboard.
|
||||
Add some colors data to your database by running the [Colors Quickstart](https://supabase.com/dashboard/project/_/sql/quickstarts) in the dashboard.
|
||||
|
||||
Then fetch the countries data using `getStaticProps` with the client from `@/utils/supabase/static-props.ts`.
|
||||
Then fetch the colors data using `getStaticProps` with the client from `@/utils/supabase/static-props.ts`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
@@ -946,7 +946,7 @@ export default function PublicPage({ data }: { data?: any[] }) {
|
||||
export async function getStaticProps() {
|
||||
const supabase = createClient()
|
||||
|
||||
const { data, error } = await supabase.from('countries').select()
|
||||
const { data, error } = await supabase.from('colors').select()
|
||||
|
||||
if (error || !data) {
|
||||
return { props: {} }
|
||||
|
||||
@@ -308,7 +308,7 @@ Create your first page. This example page calls Supabase from the server to get
|
||||
|
||||
This is an example of a public page that uses publicly readable data.
|
||||
|
||||
To populate your database, run the [countries quickstart](/dashboard/project/_/sql/quickstarts) from your dashboard.
|
||||
To populate your database, run the [colors quickstart](/dashboard/project/_/sql/quickstarts) from your dashboard.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
@@ -320,21 +320,21 @@ To populate your database, run the [countries quickstart](/dashboard/project/_/s
|
||||
import type { PageServerLoad } from './$types'
|
||||
|
||||
export const load: PageServerLoad = async ({ locals: { supabase } }) => {
|
||||
const { data: countries } = await supabase.from('countries').select('name').limit(5).order('name')
|
||||
return { countries: countries ?? [] }
|
||||
const { data: colors } = await supabase.from('colors').select('name').limit(5).order('name')
|
||||
return { colors: colors ?? [] }
|
||||
}
|
||||
```
|
||||
|
||||
```svelte src/routes/+page.svelte
|
||||
<script>
|
||||
let { data } = $props()
|
||||
let { countries } = $derived(data)
|
||||
let { colors } = $derived(data)
|
||||
</script>
|
||||
|
||||
<h1>Welcome to Supabase!</h1>
|
||||
<ul>
|
||||
{#each countries as country}
|
||||
<li>{country.name}</li>
|
||||
{#each colors as color}
|
||||
<li>{color.name}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
```
|
||||
|
||||
@@ -24,7 +24,7 @@ Setting up OAuth with Azure consists of four broad steps:
|
||||
|
||||
- Under Microsoft Entra ID, select _App registrations_ in the side panel and select _New registration._
|
||||
- Choose a name and select your preferred option for the supported account types.
|
||||
- Specify a _Web_ _Redirect URI_. It should should look like this: `https://<project-ref>.supabase.co/auth/v1/callback`
|
||||
- Specify a _Web_ _Redirect URI_. It should look like this: `https://<project-ref>.supabase.co/auth/v1/callback`
|
||||
- Finally, select _Register_ at the bottom of the screen.
|
||||
|
||||

|
||||
|
||||
@@ -80,7 +80,7 @@ Future<void> signInWithGithub() async {
|
||||
</TabPanel>
|
||||
<TabPanel id="swift" label="Swift">
|
||||
|
||||
When your user signs in, call [signInWithWithOAuth](/docs/reference/swift/auth-signinwithoauth) with `.github` as the `Provider`:
|
||||
When your user signs in, call [signInWithOAuth](/docs/reference/swift/auth-signinwithoauth) with `.github` as the `Provider`:
|
||||
|
||||
```swift
|
||||
func signInWithGithub() async throws {
|
||||
|
||||
@@ -778,3 +778,11 @@ chrome.identity.launchWebAuthFlow(
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Google consent screen
|
||||
|
||||

|
||||
|
||||
By default, the Google consent screen shows the root domain of the callback URL, where Google will send the authentication response. With Supabase Auth, it is your Supabase project's domain `(https://<your-project-ref>.supabase.co)`.
|
||||
|
||||
If that is not preferable, you can use a [Custom Domain](https://supabase.com/docs/guides/platform/custom-domains) with your Supabase project. You can use it as your project's domain when creating the Supabase client in your application and initiating the authentication flow. It will then show up in the Google consent screen. If you want your app name and the logo on the consent screen, [you must submit your app to Google for verification](https://support.google.com/cloud/answer/10311615).
|
||||
+10
-14
@@ -4,19 +4,13 @@ title: 'Firebase Auth'
|
||||
subtitle: 'Use Firebase Auth with your Supabase project'
|
||||
---
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
Creating a Firebase Auth third-party integration with your Supabase project is currently limited to a private alpha release. We are still trying to improve the developer experience around securing your project. [Register your interest in Third-Party Auth with Firebase](https://forms.supabase.com/third-party-auth-with-firebase) and the team will reach out to you.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Firebase Auth can be used as a third-party authentication provider alongside Supabase Auth, or standalone, with your Supabase project.
|
||||
|
||||
## Getting started
|
||||
|
||||
1. First you need to add an integration to connect your Supabase project with your Firebase project. You will need to get the Project ID in the [Firebase Console](https://console.firebase.google.com/u/0/project/_/settings/general).
|
||||
2. Add a new Third-party Auth integration in your project's [Authentication settings](/dashboard/project/_/settings/auth).
|
||||
3. If you are using Third Party Auth in local development, create and attach restrictive RLS policies to all tables in your public schema, Storage and Realtime to prevent unauthorized access from unrelated Firebase projects.
|
||||
3. If you are using Third Party Auth when self hosting, create and attach restrictive RLS policies to all tables in your public schema, Storage and Realtime to **prevent unauthorized access from unrelated Firebase projects**.
|
||||
4. Assign the `role: 'authenticated'` [custom user claim](https://firebase.google.com/docs/auth/admin/custom-claims) to all your users.
|
||||
5. Finally set up the Supabase client in your application.
|
||||
|
||||
@@ -33,7 +27,7 @@ import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabase = createClient('https://<supabase-project>.supabase.co', 'SUPABASE_ANON_KEY', {
|
||||
accessToken: async () => {
|
||||
;(await firebase.auth().currentUser?.getIdToken(/* forceRefresh */ false)) ?? null
|
||||
return await firebase.auth().currentUser?.getIdToken(/* forceRefresh */ false)) ?? null
|
||||
},
|
||||
})
|
||||
```
|
||||
@@ -123,19 +117,21 @@ enabled = true
|
||||
project_id = "<id>"
|
||||
```
|
||||
|
||||
## Adding an extra layer of security to your project's RLS policies
|
||||
## Adding an extra layer of security to your project's RLS policies (self-hosting only)
|
||||
|
||||
<Admonition type="note">
|
||||
<Admonition type="caution">
|
||||
|
||||
While Third-Party Auth with Firebase is in a private alpha, it is strongly recommended to follow this guide closely. Once it becomes generally available this method would not be necessary to secure your project against unauthorized access from other Firebase projects.
|
||||
**Please follow this section carefully to prevent unauthorized access to your project's data when self-hosting.**
|
||||
|
||||
You should also follow this best practice if self-hosting.
|
||||
When using the Supabase hosted platform, following this step is optional.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Firebase Auth uses a single set of JWT signing keys for all projects. This means that JWTs issued from an unrelated Firebase project to yours could access data in your Supabase project. To guard against this, creating and maintaining the following RLS policies for **all of your tables in the `public` schema** is very important. You should also attach this policy to [Storage](/docs/guides/storage/security/access-control) buckets or [Realtime](/docs/guides/realtime/authorization) channels.
|
||||
Firebase Auth uses a single set of JWT signing keys for all projects. This means that JWTs issued from an unrelated Firebase project to yours could access data in your Supabase project.
|
||||
|
||||
To achieve this we recommend using a [restrictive Postgres Row-Level Security policy](https://www.postgresql.org/docs/current/sql-createpolicy.html).
|
||||
When using the Supabase hosted platform, JWTs coming from Firebase project IDs you have not registered will be rejected before they reach your database. When self-hosting implementing this mechanism is your responsibility. An easy way to guard against this is to create and maintain the following RLS policies for **all of your tables in the `public` schema**. You should also attach this policy to [Storage](/docs/guides/storage/security/access-control) buckets or [Realtime](/docs/guides/realtime/authorization) channels.
|
||||
|
||||
It's recommended you use a [restrictive Postgres Row-Level Security policy](https://www.postgresql.org/docs/current/sql-createpolicy.html).
|
||||
|
||||
Restrictive RLS policies differ from regular (or permissive) policies in that they use the `as restrictive` clause when being defined. They do not grant permissions, but rather restrict any existing or future permissions. They're great for cases like this where the technical limitations of Firebase Auth remain separate from your app's logic.
|
||||
|
||||
|
||||
@@ -120,9 +120,12 @@ The following parameters are available for overrides:
|
||||
1. [max_worker_processes](https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-MAX-WORKER-PROCESSES) (CLI only)
|
||||
1. [session_replication_role](https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-SESSION-REPLICATION-ROLE)
|
||||
1. [shared_buffers](https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-SHARED-BUFFERS) (CLI only)
|
||||
1. [statement_timeout](https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-STATEMENT-TIMEOUT)
|
||||
1. [track_activity_query_size](https://www.postgresql.org/docs/current/runtime-config-statistics.html#GUC-TRACK-ACTIVITY-QUERY-SIZE)
|
||||
1. [track_commit_timestamp](https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-TRACK-COMMIT-TIMESTAMP)
|
||||
1. [wal_keep_size](https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-WAL-KEEP-SIZE) (CLI only)
|
||||
1. [wal_sender_timeout](https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-WAL-SENDER-TIMEOUT) (CLI only)
|
||||
1. [work_mem](https://www.postgresql.org/docs/current/runtime-config-replication.html#GUC-WAL-SENDER-TIMEOUT)
|
||||
1. [work_mem](https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-WORK-MEM)
|
||||
|
||||
#### Managing Postgres configuration with the CLI
|
||||
|
||||
@@ -148,6 +151,22 @@ postgres-config update --config max_parallel_workers=3 \
|
||||
--replace-existing-overrides
|
||||
```
|
||||
|
||||
To delete specific configuration overrides, use the `postgres-config delete` command:
|
||||
|
||||
```bash
|
||||
supabase --experimental \
|
||||
--project-ref <project-ref> \
|
||||
postgres-config delete --config shared_buffers,work_mem
|
||||
```
|
||||
|
||||
By default, this might restart the database after deleting the configurations. You can use the `--no-restart` flag to prevent the restart:
|
||||
|
||||
```bash
|
||||
supabase --experimental \
|
||||
--project-ref <project-ref> \
|
||||
postgres-config delete --config shared_buffers --no-restart
|
||||
```
|
||||
|
||||
### Resetting to default config
|
||||
|
||||
To reset a setting to its default value at the database level:
|
||||
@@ -168,6 +187,6 @@ alter role "<role_name>" set "<setting_name>" to default;
|
||||
|
||||
### Considerations
|
||||
|
||||
1. Changes through the CLI must restart the database and will cause momentary disruption to existing database connections; in most cases this should not take more than a few seconds. However, you can use the --no-restart flag to bypass the restart and keep the connections intact. Keep in mind that this depends on the specific configuration changes you’re making. if the change requires a restart, using the --no-restart flag will prevent the restart but you won’t see those changes take effect until a restart is manually triggered.
|
||||
1. Changes through the CLI might restart the database causing momentary disruption to existing database connections; in most cases this should not take more than a few seconds. However, you can use the --no-restart flag to bypass the restart and keep the connections intact. Keep in mind that this depends on the specific configuration changes you're making. if the change requires a restart, using the --no-restart flag will prevent the restart but you won't see those changes take effect until a restart is manually triggered.
|
||||
1. Custom Postgres Config will always override the default optimizations generated by Supabase. When changing compute add-ons, you should also review and update your custom Postgres Config to ensure they remain compatible and effective with the updated compute.
|
||||
1. Some parameters (e.g. `wal_keep_size`) can increase disk utilization, triggering disk expansion, which in turn can lead to [increases in your bill](/docs/guides/platform/compute-add-ons#disk-io).
|
||||
@@ -14,7 +14,7 @@ subtitle: 'Debug slow-running queries using the Postgres execution planner.'
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
-- enable explain
|
||||
alter role authenticator
|
||||
alter role authenticator
|
||||
set pgrst.db_plan_enabled to 'true';
|
||||
|
||||
-- reload the config
|
||||
@@ -28,28 +28,28 @@ To get the execution plan of a query, you can chain the `explain()` method to a
|
||||
{/* prettier-ignore */}
|
||||
```ts
|
||||
const { data, error } = await supabase
|
||||
.from('countries')
|
||||
.from('instruments')
|
||||
.select()
|
||||
.explain()
|
||||
```
|
||||
|
||||
### Example data
|
||||
|
||||
To illustrate, consider the following setup of a `countries` table:
|
||||
To illustrate, consider the following setup of a `instruments` table:
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
create table countries (
|
||||
create table instruments (
|
||||
id int8 primary key,
|
||||
name text
|
||||
);
|
||||
|
||||
insert into countries
|
||||
insert into books
|
||||
(id, name)
|
||||
values
|
||||
(1, 'Afghanistan'),
|
||||
(2, 'Albania'),
|
||||
(3, 'Algeria');
|
||||
(1, 'violin'),
|
||||
(2, 'viola'),
|
||||
(3, 'cello');
|
||||
```
|
||||
|
||||
### Expected response
|
||||
@@ -60,7 +60,7 @@ The response would typically look like this:
|
||||
```markdown
|
||||
Aggregate (cost=33.34..33.36 rows=1 width=112)
|
||||
-> Limit (cost=0.00..18.33 rows=1000 width=40)
|
||||
-> Seq Scan on countries (cost=0.00..22.00 rows=1200 width=40)
|
||||
-> Seq Scan on instruments (cost=0.00..22.00 rows=1200 width=40)
|
||||
```
|
||||
|
||||
By default, the execution plan is returned in TEXT format. However, you can also retrieve it as JSON by specifying the `format` parameter.
|
||||
@@ -97,11 +97,11 @@ To disable the `explain()` method after use, execute the following SQL commands:
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
-- disable explain
|
||||
alter role authenticator
|
||||
alter role authenticator
|
||||
set pgrst.db_plan_enabled to 'false';
|
||||
|
||||
-- if you used the above pre-request
|
||||
alter role authenticator
|
||||
alter role authenticator
|
||||
set pgrst.db_pre_request to '';
|
||||
|
||||
-- reload the config
|
||||
|
||||
@@ -4,4 +4,4 @@ title: 'pgmq: Queues'
|
||||
description: 'pgmq: Managed queues in Postgres'
|
||||
---
|
||||
|
||||
See the [Supabase Cron docs](/docs/guides/queues).
|
||||
See the [Supabase Queues docs](/docs/guides/queues).
|
||||
@@ -203,15 +203,22 @@ Sorting datasets from closest to farthest, sometimes called nearest-neighbor sor
|
||||
```sql
|
||||
create or replace function nearby_restaurants(lat float, long float)
|
||||
returns table (id public.restaurants.id%TYPE, name public.restaurants.name%TYPE, lat float, long float, dist_meters float)
|
||||
set search_path = ''
|
||||
language sql
|
||||
as $$
|
||||
select id, name, st_y(location::geometry) as lat, st_x(location::geometry) as long, st_distance(location, st_point(long, lat)::geography) as dist_meters
|
||||
select id, name, gis.st_y(location::gis.geometry) as lat, gis.st_x(location::gis.geometry) as long, gis.st_distance(location, gis.st_point(long, lat)::gis.geography) as dist_meters
|
||||
from public.restaurants
|
||||
order by location <-> st_point(long, lat)::geography;
|
||||
order by location operator(gis.<->) gis.st_point(long, lat)::gis.geography;
|
||||
$$;
|
||||
```
|
||||
|
||||
You can call this function from your client using `rpc()` like this:
|
||||
Before being able to call this function from our client we need to grant access to our `gis` schema:
|
||||
|
||||
```sql
|
||||
grant usage on schema gis to anon, authenticated;
|
||||
```
|
||||
|
||||
Now you can call this function from your client using `rpc()` like this:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -321,11 +328,12 @@ When you are working on a map-based application where the user scrolls through y
|
||||
```sql
|
||||
create or replace function restaurants_in_view(min_lat float, min_long float, max_lat float, max_long float)
|
||||
returns table (id public.restaurants.id%TYPE, name public.restaurants.name%TYPE, lat float, long float)
|
||||
set search_path to ''
|
||||
language sql
|
||||
as $$
|
||||
select id, name, st_y(location::geometry) as lat, st_x(location::geometry) as long
|
||||
select id, name, gis.st_y(location::gis.geometry) as lat, gis.st_x(location::gis.geometry) as long
|
||||
from public.restaurants
|
||||
where location && ST_SetSRID(ST_MakeBox2D(ST_Point(min_long, min_lat), ST_Point(max_long, max_lat)), 4326)
|
||||
where location operator(gis.&&) gis.ST_SetSRID(gis.ST_MakeBox2D(gis.ST_Point(min_long, min_lat), gis.ST_Point(max_long, max_lat)), 4326)
|
||||
$$;
|
||||
```
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ The data APIs automatically detect relationships between Postgres tables. Since
|
||||
|
||||
## One-to-many joins
|
||||
|
||||
Let's use an example database that stores `countries` and `cities`:
|
||||
Let's use an example database that stores `orchestral_sections` and `instruments`:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -19,50 +19,50 @@ Let's use an example database that stores `countries` and `cities`:
|
||||
>
|
||||
<TabPanel id="table" label="Tables">
|
||||
|
||||
**Countries**
|
||||
**Orchestral sections**
|
||||
|
||||
| `id` | `name` |
|
||||
| ---- | -------------- |
|
||||
| 1 | United Kingdom |
|
||||
| 2 | United States |
|
||||
| `id` | `name` |
|
||||
| ---- | --------- |
|
||||
| 1 | strings |
|
||||
| 2 | woodwinds |
|
||||
|
||||
**Cities**
|
||||
**Instruments**
|
||||
|
||||
| `id` | `name` | `country_id` |
|
||||
| ---- | ----------- | ------------ |
|
||||
| 1 | London | 1 |
|
||||
| 2 | Manchester | 1 |
|
||||
| 3 | Los Angeles | 2 |
|
||||
| 4 | New York | 2 |
|
||||
| `id` | `name` | `section_id` |
|
||||
| ---- | ------ | ------------ |
|
||||
| 1 | violin | 1 |
|
||||
| 2 | viola | 1 |
|
||||
| 3 | flute | 2 |
|
||||
| 4 | oboe | 2 |
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="SQL" label="SQL">
|
||||
|
||||
```sql
|
||||
create table countries (
|
||||
create table orchestral_sections (
|
||||
"id" serial primary key,
|
||||
"name" text
|
||||
);
|
||||
|
||||
insert into countries
|
||||
insert into orchestral_sections
|
||||
(id, name)
|
||||
values
|
||||
(1, 'United Kingdom'),
|
||||
(2, 'United States');
|
||||
(1, 'strings'),
|
||||
(2, 'woodwinds');
|
||||
|
||||
create table cities (
|
||||
create table instruments (
|
||||
"id" serial primary key,
|
||||
"name" text,
|
||||
"country_id" int references "countries"
|
||||
"section_id" int references "orchestral_sections"
|
||||
);
|
||||
|
||||
insert into cities
|
||||
(name, country_id)
|
||||
insert into instruments
|
||||
(name, section_id)
|
||||
values
|
||||
('London', 1),
|
||||
('Manchester', 1),
|
||||
('Los Angeles', 2),
|
||||
('New York', 2);
|
||||
('violin', 1),
|
||||
('viola', 1),
|
||||
('flute', 2),
|
||||
('oboe', 2);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
@@ -80,10 +80,10 @@ The APIs will automatically detect relationships based on the foreign keys:
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js
|
||||
const { data, error } = await supabase.from('countries').select(`
|
||||
id,
|
||||
name,
|
||||
cities ( id, name )
|
||||
const { data, error } = await supabase.from('orchestral_sections').select(`
|
||||
id,
|
||||
name,
|
||||
instruments ( id, name )
|
||||
`)
|
||||
```
|
||||
|
||||
@@ -93,66 +93,66 @@ const { data, error } = await supabase.from('countries').select(`
|
||||
|
||||
These helper types provide the result types from any query, including nested types for database joins.
|
||||
|
||||
Given the following schema with a relation between cities and countries:
|
||||
Given the following schema with a relation between orchestral sections and instruments:
|
||||
|
||||
```sql
|
||||
create table countries (
|
||||
create table orchestral_sections (
|
||||
"id" serial primary key,
|
||||
"name" text
|
||||
);
|
||||
|
||||
create table cities (
|
||||
create table instruments (
|
||||
"id" serial primary key,
|
||||
"name" text,
|
||||
"country_id" int references "countries"
|
||||
"section_id" int references "orchestral_sections"
|
||||
);
|
||||
```
|
||||
|
||||
We can get the nested `CountriesWithCities` type like this:
|
||||
We can get the nested `SectionsWithInstruments` type like this:
|
||||
|
||||
```ts
|
||||
import { QueryResult, QueryData, QueryError } from '@supabase/supabase-js'
|
||||
|
||||
const countriesWithCitiesQuery = supabase.from('countries').select(`
|
||||
const sectionsWithInstrumentsQuery = supabase.from('orchestral_sections').select(`
|
||||
id,
|
||||
name,
|
||||
cities (
|
||||
instruments (
|
||||
id,
|
||||
name
|
||||
)
|
||||
`)
|
||||
type CountriesWithCities = QueryData<typeof countriesWithCitiesQuery>
|
||||
type SectionsWithInstruments = QueryData<typeof sectionsWithInstrumentsQuery>
|
||||
|
||||
const { data, error } = await countriesWithCitiesQuery
|
||||
const { data, error } = await sectionsWithInstrumentsQuery
|
||||
if (error) throw error
|
||||
const countriesWithCities: CountriesWithCities = data
|
||||
const sectionsWithInstruments: SectionsWithInstruments = data
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="dart" label="Dart">
|
||||
|
||||
```dart
|
||||
final data = await supabase.from('countries').select('id, name, cities(id, name)');
|
||||
final data = await supabase.from('orchestral_sections').select('id, name, instruments(id, name)');
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="swift" label="Swift">
|
||||
|
||||
```swift
|
||||
struct Country: Codable {
|
||||
struct OrchestralSection: Codable {
|
||||
let id: Int
|
||||
let name: String
|
||||
let cities: [City]
|
||||
let instruments: [Instrument]
|
||||
|
||||
struct City: Codable {
|
||||
struct Instrument: Codable {
|
||||
let id: Int
|
||||
let name: String
|
||||
}
|
||||
}
|
||||
|
||||
let countries: [Country] = try await supabase
|
||||
.from("countries")
|
||||
.select("id, name, cities(id, name)")
|
||||
let orchestralSections: [OrchestralSection] = try await supabase
|
||||
.from("orchestral_sections")
|
||||
.select("id, name, instruments(id, name)")
|
||||
.execute()
|
||||
.value
|
||||
```
|
||||
@@ -161,14 +161,14 @@ let countries: [Country] = try await supabase
|
||||
<TabPanel id="kotlin" label="Kotlin">
|
||||
|
||||
```kotlin
|
||||
val data = supabase.from("countries").select(Columns.raw("id, name, cities(id, name)"))
|
||||
val data = supabase.from("orchestral_sections").select(Columns.raw("id, name, instruments(id, name)"))
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="python" label="Python">
|
||||
|
||||
```python
|
||||
data = supabase.from_('countries').select('id, name, cities(id, name)').execute()
|
||||
data = supabase.from_('orchestral_sections').select('id, name, instruments(id, name)').execute()
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
@@ -177,13 +177,13 @@ data = supabase.from_('countries').select('id, name, cities(id, name)').execute(
|
||||
```javascript
|
||||
const Query = `
|
||||
query {
|
||||
countriesCollection {
|
||||
orchestral_sectionsCollection {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
cities {
|
||||
id,
|
||||
name
|
||||
instruments {
|
||||
id,
|
||||
name
|
||||
}
|
||||
}
|
||||
@@ -197,7 +197,7 @@ const Query = `
|
||||
<TabPanel id="url" label="URL">
|
||||
|
||||
```bash
|
||||
GET https://[REF].supabase.co/rest/v1/countries?select=id,name,cities(id,name)
|
||||
GET https://[REF].supabase.co/rest/v1/orchestral_sections?select=id,name,instruments(id,name)
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
@@ -238,8 +238,8 @@ In these cases you don't need to explicitly define the joining table (members).
|
||||
|
||||
```js
|
||||
const { data, error } = await supabase.from('teams').select(`
|
||||
id,
|
||||
team_name,
|
||||
id,
|
||||
team_name,
|
||||
users ( id, name )
|
||||
`)
|
||||
```
|
||||
@@ -387,7 +387,7 @@ const { data, error } = await supabase.from('shifts').select(
|
||||
badge_scan_time
|
||||
),
|
||||
end_scan:scans!scan_id_end (
|
||||
id,
|
||||
id,
|
||||
user_id,
|
||||
badge_scan_time
|
||||
)
|
||||
|
||||
@@ -61,7 +61,7 @@ The default role timeouts are:
|
||||
|
||||
- `anon`: 3s
|
||||
- `authenticated`: 8s
|
||||
- `service_role`: none (capped by API to be 60s)
|
||||
- `service_role`: none (defaults to the `authenticator` role's 8s timeout if unset)
|
||||
- `postgres`: none (capped by default global timeout to be 2min)
|
||||
|
||||
Run the following query to change a role's timeout:
|
||||
|
||||
@@ -140,7 +140,7 @@ If you plan to solely use Prisma instead of the Supabase Data API (PostgREST), t
|
||||
In your .env file, set the DATABASE_URL variable to your connection string
|
||||
```text .env
|
||||
# Used for Prisma Migrations and within your application
|
||||
DATABASE_URL="postgres://[DB-USER].[PROJECT-REF]:[PRISMA-PASSWORD]@aws-0-us-east-1.pooler.supabase.com:5432/postgres"
|
||||
DATABASE_URL="postgres://[DB-USER].[PROJECT-REF]:[PRISMA-PASSWORD]@[DB-REGION].pooler.supabase.com:5432/postgres"
|
||||
```
|
||||
|
||||
Change your string's `[DB-USER]` to `prisma` and add the password you created in step 1
|
||||
|
||||
@@ -10,12 +10,6 @@ To ensure that queries return the expected data, RLS policies are correctly appl
|
||||
|
||||
- Secondly, you can test through the Supabase CLI, which is a more low-level approach where you write tests in SQL.
|
||||
|
||||
# Testing through the Supabase client
|
||||
|
||||
A good starting point are the tests written for the Supabase client itself. For example, [here](https://github.com/supabase/supabase/tree/master/tests/features/javascript) are the tests for the Supabase JS client written with TypeScript and Jest.
|
||||
|
||||
Keep in mind that the tests (which you'll likely integrate into your CI) need to run with the same DB setup that you're running locally. That means you need to make all changes to the database exclusively through migrations (not manually).
|
||||
|
||||
# Testing using the Supabase CLI
|
||||
|
||||
You can use the Supabase CLI to test your database. The minimum required version of the CLI is [v1.11.4](https://github.com/supabase/cli/releases). To get started:
|
||||
|
||||
Loaded 100 of 1042 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user