mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 07:09:07 +03:00
114 files changed
+3048
-3586
No files matched your search
@@ -6,13 +6,20 @@ description: Want to help?
|
||||
|
||||
## How to contribute
|
||||
|
||||
Supabase has many [open source repos](https://github.com/orgs/supabase/repositories). Some of the more popular ones are:
|
||||
- [Supabase](https://github.com/supabase/supabase) - Our Dashboard, Websites, and example apps
|
||||
- [Supabase-js](https://github.com/supabase/supabase-js) - A clientside library written in Typescript
|
||||
- [Gotrue](https://github.com/supabase/gotrue) - Our auth server, written in Go
|
||||
- [Realtime](https://github.com/supabase/realtime) - A server for streaming changes from Postgres, written in Elixir
|
||||
- [browse all]((https://github.com/orgs/supabase/repositories))
|
||||
|
||||
Want to contribute? Why not jump into our GitHub repo and:
|
||||
|
||||
- [Sponsor Supabase](https://github.com/sponsors/supabase).
|
||||
- [Answer Discussions](https://github.com/supabase/supabase/discussions).
|
||||
- [Submit an issue](https://github.com/supabase/supabase/issue)
|
||||
- Submit an issue to one of our [repos](https://github.com/orgs/supabase/repositories)
|
||||
- Report a performance issue or a part of the documentation that you find confusing.
|
||||
- [Create a pull request](https://github.com/supabase/supabase/pulls)
|
||||
- Create a pull request in one of our [repos](https://github.com/orgs/supabase/repositories)
|
||||
- [Translate our Readme](https://github.com/supabase/supabase/issues/1341).
|
||||
- Try our products and give feedback.
|
||||
- Spread the word if you like what we are doing.
|
||||
- Spread the word if you like what we are doing.
|
||||
@@ -10,6 +10,8 @@ SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZX
|
||||
|
||||
## General
|
||||
SITE_URL=http://localhost:3000
|
||||
KONG_URL=http://kong:8000
|
||||
META_URL=http://meta:8080
|
||||
ADDITIONAL_REDIRECT_URLS=
|
||||
JWT_EXPIRY=3600
|
||||
DISABLE_SIGNUP=false
|
||||
|
||||
@@ -14,8 +14,8 @@ services:
|
||||
ports:
|
||||
- ${STUDIO_PORT}:3000/tcp
|
||||
environment:
|
||||
SUPABASE_URL: http://kong:8000
|
||||
STUDIO_PG_META_URL: http://meta:8080
|
||||
SUPABASE_URL: ${KONG_URL}
|
||||
STUDIO_PG_META_URL: ${META_URL}
|
||||
SUPABASE_ANON_KEY: ${ANON_KEY}
|
||||
SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY}
|
||||
|
||||
|
||||
+4
-1
@@ -1,2 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||
+1545
-3156
File diff suppressed because it is too large.
Load diff
@@ -1,6 +1,4 @@
|
||||
{
|
||||
"name": "nextjs-ts-user-management",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -9,9 +7,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@supabase/supabase-js": "^1.7.6",
|
||||
"next": "10.0.9",
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1"
|
||||
"next": "12.0.8",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^17.0.3",
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@@ -12,8 +16,15 @@
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve"
|
||||
"jsx": "preserve",
|
||||
"incremental": true
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
+3
-3
@@ -7,12 +7,12 @@
|
||||
# docker builder prune
|
||||
|
||||
|
||||
FROM node:14 as base
|
||||
FROM node:14-slim as base
|
||||
WORKDIR /usr/src/app
|
||||
# Do `npm ci` separately so we can cache `node_modules`
|
||||
# https://nodejs.org/en/docs/guides/nodejs-docker-webapp/
|
||||
COPY package*.json ./
|
||||
RUN npm clean-install
|
||||
RUN npm clean-install && npm cache clean --force
|
||||
COPY . .
|
||||
|
||||
FROM base as dev
|
||||
@@ -20,6 +20,6 @@ EXPOSE 8082
|
||||
CMD ["npm", "run", "dev"]
|
||||
|
||||
FROM base as production
|
||||
RUN npm run build
|
||||
RUN npm run build && rm -rf .next/cache/webpack && npm prune --production
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "run", "start"]
|
||||
@@ -72,3 +72,19 @@ Then run the following commands to install dependencies and start the dashboard.
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## UI Testing Notes
|
||||
|
||||
### `<Popover>` vs `<Dropdown>`
|
||||
|
||||
When simulating clicks on these components, do the following:
|
||||
|
||||
```js
|
||||
// for Popovers
|
||||
import userEvent from '@testing-library/user-event'
|
||||
userEvent.click('Hello world')
|
||||
|
||||
// for Popovers
|
||||
import clickDropdown from 'tests/helpers'
|
||||
clickDropdown('Hello world')
|
||||
```
|
||||
@@ -107,7 +107,7 @@ const PgbouncerConfig: FC<Props> = ({ projectRef, config }) => {
|
||||
suitable mode for your use case,{' '}
|
||||
<Typography.Link
|
||||
target="_blank"
|
||||
href="https://supabase.com/blog/2021/04/02/supabase-pgbouncer#pool-modes"
|
||||
href="https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pool"
|
||||
>
|
||||
click here
|
||||
</Typography.Link>
|
||||
|
||||
@@ -21,6 +21,9 @@ import { ProjectUsageMinimal } from 'components/to-be-cleaned/Usage'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { API_URL, METRICS, DATE_FORMAT } from 'lib/constants'
|
||||
import { useFlag } from 'hooks'
|
||||
import { TooltipProps } from 'recharts'
|
||||
import { TooltipType } from 'recharts/types/util/types'
|
||||
import { TooltipContentProps, TooltipTriggerProps } from '@radix-ui/react-tooltip'
|
||||
|
||||
const CHART_INTERVALS = [
|
||||
{ key: 'minutely', label: '60 minutes', startValue: 1, startUnit: 'hour', format: 'MMM D, h:ma' },
|
||||
@@ -32,6 +35,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const ProjectUsage: FC<Props> = ({ project }) => {
|
||||
const logsTimestampFilter = useFlag('logsTimestampFilter')
|
||||
const logsUsageChartIntervals = useFlag('logsUsageChartIntervals')
|
||||
const [interval, setInterval] = useState<string>('minutely')
|
||||
const router = useRouter()
|
||||
@@ -53,6 +57,21 @@ const ProjectUsage: FC<Props> = ({ project }) => {
|
||||
const endDate = dayjs().format(DATE_FORMAT)
|
||||
const charts = data?.data
|
||||
const datetimeFormat = selectedInterval.format || 'MMM D, ha'
|
||||
const handleBarClick = (v: any) => {
|
||||
if (!logsTimestampFilter) return
|
||||
if (!v || !v.activePayload?.[0]?.payload) return
|
||||
// returns rechart internal tooltip data type
|
||||
const payload = v.activePayload[0].payload
|
||||
const timestamp = payload.timestamp
|
||||
const timestampDigits = String(timestamp).length
|
||||
if (timestampDigits < 16) {
|
||||
// pad unix timestamp with additional 0 and then forward
|
||||
const paddedTimestamp = String(timestamp) + '0'.repeat(16 - timestampDigits)
|
||||
router.push(`/project/${ref}/settings/logs/rest?te=${paddedTimestamp}`)
|
||||
} else {
|
||||
router.push(`/project/${ref}/settings/logs/rest?te=${timestamp}`)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="mx-6 space-y-6">
|
||||
<div className="flex flex-row justify-between align-center w-full">
|
||||
@@ -99,6 +118,7 @@ const ProjectUsage: FC<Props> = ({ project }) => {
|
||||
customDateFormat={datetimeFormat}
|
||||
data={charts}
|
||||
isLoading={!charts && !error ? true : false}
|
||||
onBarClick={handleBarClick}
|
||||
/>
|
||||
<ProjectUsageMinimal
|
||||
projectRef={project.ref}
|
||||
@@ -125,6 +145,7 @@ const ProjectUsage: FC<Props> = ({ project }) => {
|
||||
customDateFormat={datetimeFormat}
|
||||
data={charts}
|
||||
isLoading={!charts && !error ? true : false}
|
||||
onBarClick={handleBarClick}
|
||||
/>
|
||||
<ProjectUsageMinimal
|
||||
projectRef={project.ref}
|
||||
@@ -151,6 +172,7 @@ const ProjectUsage: FC<Props> = ({ project }) => {
|
||||
customDateFormat={datetimeFormat}
|
||||
data={charts}
|
||||
isLoading={!charts && !error ? true : false}
|
||||
onBarClick={handleBarClick}
|
||||
/>
|
||||
<ProjectUsageMinimal
|
||||
projectRef={project.ref}
|
||||
@@ -173,6 +195,7 @@ const ProjectUsage: FC<Props> = ({ project }) => {
|
||||
customDateFormat={datetimeFormat}
|
||||
data={charts}
|
||||
isLoading={!charts && !error ? true : false}
|
||||
onBarClick={handleBarClick}
|
||||
/>
|
||||
{/* Empty space just so the cards are of the same height */}
|
||||
<div className="py-[26px]" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import React, { FC, SyntheticEvent, useEffect, useMemo, useState } from 'react'
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
@@ -9,21 +9,28 @@ import {
|
||||
IconX,
|
||||
Toggle,
|
||||
IconSearch,
|
||||
IconClock,
|
||||
Popover,
|
||||
} from '@supabase/ui'
|
||||
import { LogTemplate } from '.'
|
||||
|
||||
import { LogSearchCallback, LogTemplate } from '.'
|
||||
import dayjs from 'dayjs'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
import Flag from 'components/ui/Flag/Flag'
|
||||
interface Props {
|
||||
defaultSearchValue?: string
|
||||
defaultFromValue?: string
|
||||
templates?: any
|
||||
isLoading: boolean
|
||||
isCustomQuery: boolean
|
||||
newCount: number
|
||||
onRefresh?: () => void
|
||||
onSearch?: (query: string) => void
|
||||
onSearch?: LogSearchCallback
|
||||
onCustomClick?: () => void
|
||||
onSelectTemplate: (template: LogTemplate) => void
|
||||
}
|
||||
|
||||
dayjs.extend(utc)
|
||||
|
||||
/**
|
||||
* Logs control panel header + wrapper
|
||||
*/
|
||||
@@ -35,11 +42,13 @@ const LogPanel: FC<Props> = ({
|
||||
onRefresh,
|
||||
onSearch = () => {},
|
||||
defaultSearchValue = '',
|
||||
defaultFromValue = '',
|
||||
onCustomClick,
|
||||
onSelectTemplate,
|
||||
}) => {
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
const [from, setFrom] = useState({ value: '', error: '' })
|
||||
const [defaultTimestamp, setDefaultTimestamp] = useState(dayjs().utc().toISOString())
|
||||
// sync local state with provided default value
|
||||
useEffect(() => {
|
||||
if (search !== defaultSearchValue) {
|
||||
@@ -47,6 +56,30 @@ const LogPanel: FC<Props> = ({
|
||||
}
|
||||
}, [defaultSearchValue])
|
||||
|
||||
useEffect(() => {
|
||||
if (from.value !== defaultFromValue) {
|
||||
setFrom({ value: defaultFromValue, error: '' })
|
||||
}
|
||||
}, [defaultFromValue])
|
||||
|
||||
const handleFromChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value
|
||||
if (value !== '' && isNaN(Date.parse(value))) {
|
||||
setFrom({ value, error: 'Invalid ISO 8601 timestamp' })
|
||||
} else {
|
||||
setFrom({ value, error: '' })
|
||||
}
|
||||
}
|
||||
const handleFromReset = async () => {
|
||||
setFrom({ value: '', error: '' })
|
||||
const value = dayjs().utc().toISOString()
|
||||
setDefaultTimestamp(value)
|
||||
onSearch({ query: search, from: '' })
|
||||
}
|
||||
|
||||
const handleSearch = () => onSearch({ query: search, from: from.value })
|
||||
|
||||
const showFromReset = from.value !== ''
|
||||
return (
|
||||
<div className="bg-panel-header-light dark:bg-panel-header-dark">
|
||||
<div className="px-2 py-1 flex items-center justify-between w-full">
|
||||
@@ -86,7 +119,7 @@ const LogPanel: FC<Props> = ({
|
||||
</Dropdown.Item>
|
||||
))}
|
||||
>
|
||||
<Button type="text" iconRight={<IconChevronDown />}>
|
||||
<Button as="span" type="text" iconRight={<IconChevronDown />}>
|
||||
Templates
|
||||
</Button>
|
||||
</Dropdown>
|
||||
@@ -99,43 +132,98 @@ const LogPanel: FC<Props> = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-4">
|
||||
{/* wrap with form so that if user presses enter, the search value will submit automatically */}
|
||||
{!isCustomQuery && (
|
||||
<form
|
||||
id="log-panel-search"
|
||||
onSubmit={(e) => {
|
||||
// prevent redirection
|
||||
e.preventDefault()
|
||||
onSearch(search)
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
placeholder="Search events"
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
value={search}
|
||||
actions={[
|
||||
search && (
|
||||
<IconX
|
||||
key="clear-search"
|
||||
size="tiny"
|
||||
className="cursor-pointer mx-1"
|
||||
title="Clear search"
|
||||
onClick={() => setSearch('')}
|
||||
/>
|
||||
),
|
||||
|
||||
<Button
|
||||
key="go"
|
||||
size="tiny"
|
||||
title="Go"
|
||||
type="secondary"
|
||||
onClick={() => onSearch(search)}
|
||||
<>
|
||||
<Flag name="logsTimestampFilter">
|
||||
<div className="flex flex-row">
|
||||
<Popover
|
||||
side="bottom"
|
||||
align="end"
|
||||
portalled
|
||||
overlay={
|
||||
<Input
|
||||
label="From"
|
||||
labelOptional="UTC"
|
||||
value={from.value === '' ? defaultTimestamp : from.value}
|
||||
onChange={handleFromChange}
|
||||
error={from.error}
|
||||
className="w-72 p-3"
|
||||
actions={[
|
||||
from.value && (
|
||||
<IconX
|
||||
key="reset-from"
|
||||
size="tiny"
|
||||
className="cursor-pointer mx-1"
|
||||
title="Reset"
|
||||
onClick={handleFromReset}
|
||||
/>
|
||||
),
|
||||
<Button
|
||||
key="set"
|
||||
size="tiny"
|
||||
title="Set"
|
||||
type="secondary"
|
||||
onClick={handleSearch}
|
||||
>
|
||||
Set
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<IconSearch size={16} />
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
</form>
|
||||
<Button
|
||||
as="span"
|
||||
size="tiny"
|
||||
className={showFromReset ? '!rounded-r-none' : ''}
|
||||
type={showFromReset ? 'outline' : 'text'}
|
||||
icon={<IconClock size="tiny" />}
|
||||
>
|
||||
{from.value ? 'Custom' : 'Now'}
|
||||
</Button>
|
||||
</Popover>
|
||||
{showFromReset && (
|
||||
<Button
|
||||
size="tiny"
|
||||
className={showFromReset ? '!rounded-l-none' : ''}
|
||||
icon={<IconX size="tiny" />}
|
||||
type="outline"
|
||||
title="Clear timestamp filter"
|
||||
onClick={handleFromReset}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Flag>
|
||||
{/* wrap with form so that if user presses enter, the search value will submit automatically */}
|
||||
<form
|
||||
id="log-panel-search"
|
||||
onSubmit={(e) => {
|
||||
// prevent redirection
|
||||
e.preventDefault()
|
||||
handleSearch()
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
placeholder="Search events"
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
value={search}
|
||||
actions={[
|
||||
search && (
|
||||
<IconX
|
||||
key="clear-search"
|
||||
size="tiny"
|
||||
className="cursor-pointer mx-1"
|
||||
title="Clear search"
|
||||
onClick={() => setSearch('')}
|
||||
/>
|
||||
),
|
||||
|
||||
<Button key="go" size="tiny" title="Go" type="secondary" onClick={handleSearch}>
|
||||
<IconSearch size={16} />
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
interface Metadata {
|
||||
[key: string]: string | number | Object | Object[]
|
||||
}
|
||||
|
||||
export type LogSearchCallback = (filters: { query: string; from: string }) => void
|
||||
export interface LogData {
|
||||
id: string
|
||||
timestamp: number
|
||||
|
||||
+7
-3
@@ -13,8 +13,12 @@ const TwoOptionToggle: FC<Props> = ({
|
||||
onClickOption,
|
||||
borderOverride = 'border-gray-600 dark:border-gray-800',
|
||||
}) => {
|
||||
const buttonStyle = `absolute top-0 z-1 text-xs inline-flex h-full items-center justify-center font-medium
|
||||
hover:text-white focus:z-10 focus:outline-none focus:border-blue-300 focus:ring-blue active:bg-gray-100
|
||||
const buttonStyle = (
|
||||
isActive: boolean
|
||||
) => `absolute top-0 z-1 text-xs inline-flex h-full items-center justify-center font-medium
|
||||
${
|
||||
isActive ? 'hover:text-white' : 'hover:text-gray-600'
|
||||
} dark:hover:text-white focus:z-10 focus:outline-none focus:border-blue-300 focus:ring-blue active:bg-gray-100
|
||||
transition ease-in-out duration-150`
|
||||
|
||||
return (
|
||||
@@ -36,7 +40,7 @@ const TwoOptionToggle: FC<Props> = ({
|
||||
className={`
|
||||
${activeOption === option ? 'text-gray-200' : 'text-gray-400'}
|
||||
${index === 0 ? 'right-0' : 'left-0'}
|
||||
${buttonStyle}
|
||||
${buttonStyle(activeOption === option)}
|
||||
cursor-pointer
|
||||
`}
|
||||
onClick={() => onClickOption(option)}
|
||||
|
||||
@@ -15,16 +15,15 @@ interface Props {
|
||||
|
||||
const AuthLayout: FC<Props> = ({ title, children }) => {
|
||||
const { ui, meta } = useStore()
|
||||
const { isLoading, error } = meta.tables
|
||||
const { isInitialized, isLoading, error } = meta.tables
|
||||
const projectRef = ui.selectedProject?.ref ?? 'default'
|
||||
|
||||
const router = useRouter()
|
||||
const page = router.pathname.split('/')[4]
|
||||
|
||||
const [loaded, setLoaded] = useState<boolean>(false)
|
||||
const [loaded, setLoaded] = useState<boolean>(isInitialized)
|
||||
|
||||
useEffect(() => {
|
||||
// I think we should just shift the loading of things into the page level
|
||||
meta.tables.load()
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -15,13 +15,13 @@ interface Props {
|
||||
|
||||
const DatabaseLayout: FC<Props> = ({ title, children }) => {
|
||||
const { meta, ui } = useStore()
|
||||
const { isLoading, error } = meta.tables
|
||||
const { isInitialized, isLoading, error } = meta.tables
|
||||
const project = ui.selectedProject
|
||||
|
||||
const router = useRouter()
|
||||
const page = router.pathname.split('/')[4]
|
||||
|
||||
const [loaded, setLoaded] = useState<boolean>(false)
|
||||
const [loaded, setLoaded] = useState<boolean>(isInitialized)
|
||||
|
||||
useEffect(() => {
|
||||
// Eventually should only load the required stores based on the pages
|
||||
|
||||
@@ -23,7 +23,7 @@ interface Props {
|
||||
hideIconBar?: boolean
|
||||
}
|
||||
|
||||
const BaseLayout: FC<Props> = ({
|
||||
const ProjectLayout: FC<Props> = ({
|
||||
title,
|
||||
isLoading = false,
|
||||
product = '',
|
||||
@@ -88,4 +88,4 @@ const BaseLayout: FC<Props> = ({
|
||||
)
|
||||
}
|
||||
|
||||
export default observer(BaseLayout)
|
||||
export default observer(ProjectLayout)
|
||||
@@ -14,9 +14,9 @@ interface Props {
|
||||
|
||||
const SQLEditorLayout: FC<Props> = ({ title, children }) => {
|
||||
const { content } = useStore()
|
||||
const { isLoading, error } = content
|
||||
const { isInitialized, isLoading, error } = content
|
||||
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const [loaded, setLoaded] = useState(isInitialized)
|
||||
|
||||
useEffect(() => {
|
||||
content.load()
|
||||
|
||||
@@ -227,7 +227,7 @@ const SideBarContent = observer(() => {
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<Loading active={contentStore.isLoading}>
|
||||
<Loading active={contentStore.isInitialized}>
|
||||
<div className="px-3 space-y-6">
|
||||
{favouriteTabs.length >= 1 && (
|
||||
<div className="editor-product-menu">
|
||||
|
||||
@@ -28,9 +28,9 @@ const TableEditorLayout: FC<Props> = ({
|
||||
children,
|
||||
}) => {
|
||||
const { meta } = useStore()
|
||||
const { isLoading, error } = meta.tables
|
||||
const { isInitialized, isLoading, error } = meta.tables
|
||||
|
||||
const [loaded, setLoaded] = useState<boolean>(false)
|
||||
const [loaded, setLoaded] = useState<boolean>(isInitialized)
|
||||
|
||||
useEffect(() => {
|
||||
meta.schemas.load()
|
||||
|
||||
@@ -97,7 +97,7 @@ const TableEditorMenu: FC<Props> = ({
|
||||
<div className="my-6 flex flex-col flex-grow">
|
||||
{/* Schema selection dropdown */}
|
||||
<div className="mx-4">
|
||||
{meta.schemas.isLoading ? (
|
||||
{!meta.schemas.isInitialized ? (
|
||||
<div className="h-[30px] border border-gray-500 px-3 rounded flex items-center space-x-3">
|
||||
<IconLoader className="animate-spin" size={14} />
|
||||
<Typography.Text small>Loading schemas...</Typography.Text>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { API_URL } from 'lib/constants'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { BarChart, AreaChart } from './ChartRenderer'
|
||||
import { ChartData } from './ChartHandler.types'
|
||||
import { TooltipProps } from 'recharts'
|
||||
|
||||
interface Props {
|
||||
label: string
|
||||
@@ -30,6 +31,7 @@ interface Props {
|
||||
hideChartType?: boolean
|
||||
data?: ChartData
|
||||
isLoading?: boolean
|
||||
onBarClick?: (v: any) => void
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,6 +57,7 @@ const ChartHandler: FC<Props> = ({
|
||||
hideChartType = false,
|
||||
data,
|
||||
isLoading,
|
||||
onBarClick,
|
||||
}) => {
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
@@ -169,6 +172,7 @@ const ChartHandler: FC<Props> = ({
|
||||
highlightedValue={highlightedValue}
|
||||
label={label}
|
||||
customDateFormat={customDateFormat}
|
||||
onBarClick={onBarClick}
|
||||
/>
|
||||
) : (
|
||||
<AreaChart
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Tooltip,
|
||||
Cell,
|
||||
ResponsiveContainer,
|
||||
TooltipProps,
|
||||
} from 'recharts'
|
||||
|
||||
import dayjs from 'dayjs'
|
||||
@@ -17,6 +18,7 @@ import customParseFormat from 'dayjs/plugin/customParseFormat'
|
||||
import timezone from 'dayjs/plugin/timezone'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
import { formatBytes } from 'lib/helpers'
|
||||
import { TooltipType } from 'recharts/types/util/types'
|
||||
|
||||
dayjs.extend(customParseFormat)
|
||||
dayjs.extend(timezone)
|
||||
@@ -115,11 +117,6 @@ const NoData = () => (
|
||||
<IconBarChart2 />
|
||||
</Typography.Text>
|
||||
<Typography.Text>No data to show</Typography.Text>
|
||||
<div className="text-center">
|
||||
<Typography.Text small className="opacity-50">
|
||||
May take 24 hours for data to appear
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -146,6 +143,7 @@ export function BarChart({
|
||||
highlightedValue,
|
||||
customDateFormat,
|
||||
label,
|
||||
onBarClick,
|
||||
}: any) {
|
||||
const hasData = data ? dataCheck(data, attribute) : true
|
||||
|
||||
@@ -190,9 +188,13 @@ export function BarChart({
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
className="overflow-visible"
|
||||
className="overflow-visible cursor-pointer"
|
||||
onMouseMove={onMouseMove}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onClick={(tooltipData: any) => {
|
||||
// receives tooltip data https://github.com/recharts/recharts/blob/2a3405ff64a0c050d2cf94c36f0beef738d9e9c2/src/chart/generateCategoricalChart.tsx
|
||||
if (onBarClick) onBarClick(tooltipData)
|
||||
}}
|
||||
>
|
||||
<XAxis
|
||||
dataKey="period_start"
|
||||
@@ -217,7 +219,9 @@ export function BarChart({
|
||||
{data?.map((entry: any, index: any) => (
|
||||
<Cell
|
||||
key={`cell-${index}`}
|
||||
className="transition-all duration-300"
|
||||
className={`transition-all duration-300 ${
|
||||
onBarClick ? 'cursor-pointer' : ''
|
||||
}`}
|
||||
fill={
|
||||
focusBar === index || mouseLeave ? '#3ecf8e' : 'rgba(62, 207, 142, 0.2)'
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export const DisplayApiSettings = () => {
|
||||
|
||||
// Get the API service
|
||||
const apiService = (data?.services ?? []).find((x: any) => x.app.id == API_SERVICE_ID)
|
||||
const apiKeys = apiService.service_api_keys
|
||||
const apiKeys = apiService?.service_api_keys ?? []
|
||||
|
||||
return (
|
||||
<ApiContentWrapper>
|
||||
|
||||
@@ -3,16 +3,16 @@ import { useRouter } from 'next/router'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useStore, withAuth } from 'hooks'
|
||||
|
||||
import BaseLayout from 'components/layouts'
|
||||
import ProjectLayout from 'components/layouts'
|
||||
|
||||
type ProjectBuildingPageState = {} & any
|
||||
const ProjectBuildingPage: React.FC<ProjectBuildingPageState> = () => {
|
||||
const { ui } = useStore()
|
||||
const project: any = ui.selectedProject
|
||||
return (
|
||||
<BaseLayout title="Project Building">
|
||||
<ProjectLayout title="Project Building">
|
||||
<RedirectToDashboard projectRef={project?.ref ?? ''} />
|
||||
</BaseLayout>
|
||||
</ProjectLayout>
|
||||
)
|
||||
}
|
||||
export default withAuth(observer(ProjectBuildingPage))
|
||||
@@ -24,7 +24,10 @@ const RedirectToDashboard: React.FC<RedirectToDashboardState> = ({ projectRef })
|
||||
const router = useRouter()
|
||||
|
||||
React.useEffect(() => {
|
||||
router.push(`/project/${projectRef}`)
|
||||
// Use redirect to reload store data properly after project has been
|
||||
// been created or unpaused, this is necessarily especially for unpausing
|
||||
// so that the dashboard fetches the updated connection strings
|
||||
window.location.replace(`/project/${projectRef}`)
|
||||
}, [])
|
||||
return null
|
||||
}
|
||||
@@ -26,12 +26,14 @@ import {
|
||||
LogTemplate,
|
||||
TEMPLATES,
|
||||
LogData,
|
||||
LogSearchCallback,
|
||||
} from 'components/interfaces/Settings/Logs'
|
||||
import { uuidv4 } from 'lib/helpers'
|
||||
import useSWRInfinite from 'swr/infinite'
|
||||
import { isUndefined } from 'lodash'
|
||||
import Flag from 'components/ui/Flag/Flag'
|
||||
import { useFlag } from 'hooks'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
/**
|
||||
* Acts as a container component for the entire log display
|
||||
@@ -48,7 +50,7 @@ export const LogPage: NextPage = () => {
|
||||
const logsQueryParamsSyncing = useFlag('logsQueryParamsSyncing')
|
||||
const logsCustomSql = useFlag('logsCustomSql')
|
||||
const router = useRouter()
|
||||
const { ref, type, q, s, ts } = router.query
|
||||
const { ref, type, q, s, te } = router.query
|
||||
const [editorId, setEditorId] = useState<string>(uuidv4())
|
||||
const [editorValue, setEditorValue] = useState('')
|
||||
const [mode, setMode] = useState<'simple' | 'custom'>('simple')
|
||||
@@ -82,8 +84,10 @@ export const LogPage: NextPage = () => {
|
||||
searchString: s as string,
|
||||
})
|
||||
}
|
||||
if (ts) {
|
||||
setParams({ ...params, timestamp_start: ts as string })
|
||||
if (te) {
|
||||
setParams((prev) => ({ ...prev, timestamp_end: te as string }))
|
||||
} else {
|
||||
setParams((prev) => ({ ...prev, timestamp_end: '' }))
|
||||
}
|
||||
}, [logsQueryParamsSyncing])
|
||||
|
||||
@@ -105,7 +109,7 @@ export const LogPage: NextPage = () => {
|
||||
if (prevPageData === null) {
|
||||
// reduce interval window limit by using the timestamp of the last log
|
||||
queryParams = genQueryParams(params)
|
||||
} else if (prevPageData.data.length === 0) {
|
||||
} else if ((prevPageData?.data ?? []).length === 0) {
|
||||
// no rows returned, indicates that no more data to retrieve and append.
|
||||
return null
|
||||
} else {
|
||||
@@ -147,7 +151,14 @@ export const LogPage: NextPage = () => {
|
||||
|
||||
const handleRefresh = () => {
|
||||
setLatestRefresh(new Date().toISOString())
|
||||
setParams({ ...params, timestamp_start: '' })
|
||||
setParams({ ...params, timestamp_end: '' })
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: {
|
||||
...router.query,
|
||||
te: undefined,
|
||||
},
|
||||
})
|
||||
setSize(1)
|
||||
}
|
||||
|
||||
@@ -171,6 +182,7 @@ export const LogPage: NextPage = () => {
|
||||
where: isSelectQuery ? '' : template.searchString,
|
||||
sql: isSelectQuery ? template.searchString : '',
|
||||
search_query: '',
|
||||
timestamp_end: '',
|
||||
}))
|
||||
setEditorId(uuidv4())
|
||||
}
|
||||
@@ -189,18 +201,27 @@ export const LogPage: NextPage = () => {
|
||||
...router.query,
|
||||
q: editorValue,
|
||||
s: undefined,
|
||||
te: undefined,
|
||||
},
|
||||
})
|
||||
}
|
||||
const handleSearch = (v: string) => {
|
||||
setParams((prev) => ({ ...prev, search_query: v || '', where: '', sql: '' }))
|
||||
const handleSearch: LogSearchCallback = ({ query, from }) => {
|
||||
const unixMicro = dayjs(from).valueOf() * 1000
|
||||
setParams((prev) => ({
|
||||
...prev,
|
||||
search_query: query || '',
|
||||
timestamp_end: from ? String(unixMicro) : '',
|
||||
where: '',
|
||||
sql: '',
|
||||
}))
|
||||
if (!logsQueryParamsSyncing) return
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: {
|
||||
...router.query,
|
||||
q: undefined,
|
||||
s: v || '',
|
||||
s: query || '',
|
||||
te: unixMicro,
|
||||
},
|
||||
})
|
||||
setEditorValue('')
|
||||
@@ -217,6 +238,9 @@ export const LogPage: NextPage = () => {
|
||||
onRefresh={handleRefresh}
|
||||
onSearch={handleSearch}
|
||||
defaultSearchValue={params.search_query}
|
||||
defaultFromValue={
|
||||
params.timestamp_end ? dayjs(Number(params.timestamp_end) / 1000).toISOString() : ''
|
||||
}
|
||||
onCustomClick={handleModeToggle}
|
||||
onSelectTemplate={onSelectTemplate}
|
||||
/>
|
||||
|
||||
@@ -23,6 +23,12 @@ export interface IPostgresMetaInterface<T> {
|
||||
byId: (id: number | string) => T | undefined
|
||||
initialDataArray: (value: T[]) => void
|
||||
}
|
||||
|
||||
// [TODO] Need to refactor the logic for 'isInitialized'
|
||||
// Should avoid page level "loaded" state like TableEditorLayout
|
||||
// The logic should be - state should be INITIAL at first, and only
|
||||
// taken as initialized after the first trigger of the load method.
|
||||
// https://github.com/supabase/supabase/pull/5128#pullrequestreview-860706726
|
||||
export default class PostgresMetaInterface<T> implements IPostgresMetaInterface<T> {
|
||||
STATES = {
|
||||
INITIAL: 'initial',
|
||||
|
||||
@@ -11,6 +11,7 @@ import { API_URL } from 'lib/constants'
|
||||
|
||||
export interface IProjectContentStore {
|
||||
isLoading: boolean
|
||||
isInitialized: boolean
|
||||
error: any
|
||||
|
||||
load: () => void
|
||||
@@ -47,6 +48,10 @@ export default class ProjectContentStore implements IProjectContentStore {
|
||||
return this.state === this.STATES.INITIAL || this.state === this.STATES.LOADING
|
||||
}
|
||||
|
||||
get isInitialized() {
|
||||
return this.state !== this.STATES.INITIAL
|
||||
}
|
||||
|
||||
async fetchData() {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -7,12 +7,13 @@ import { get } from 'lib/common/fetch'
|
||||
jest.mock('next/router')
|
||||
import { useRouter } from 'next/router'
|
||||
import { clickDropdown } from 'tests/helpers'
|
||||
useRouter.mockReturnValue({ query: { ref: '123' } })
|
||||
const mockPush = jest.fn()
|
||||
useRouter.mockReturnValue({ query: { ref: '123' }, push: mockPush })
|
||||
|
||||
// need to wrap component with SWRConfig in order to clear cache between tests
|
||||
// TODO: abstract out to global setup
|
||||
import { SWRConfig } from 'swr'
|
||||
const ProjectUsage = jest.fn()
|
||||
const ProjectUsage = jest.fn()
|
||||
ProjectUsage.mockImplementation((props) => {
|
||||
const Original = jest.requireActual('components/interfaces/Home/ProjectUsage').default
|
||||
// wrap with SWR to reset the cache each time
|
||||
@@ -30,10 +31,9 @@ jest.mock('hooks')
|
||||
import { useFlag } from 'hooks'
|
||||
useFlag.mockReturnValue(true)
|
||||
|
||||
|
||||
|
||||
beforeEach(() => {
|
||||
get.mockReset()
|
||||
mockPush.mockReset()
|
||||
})
|
||||
|
||||
const MOCK_CHART_DATA = {
|
||||
@@ -71,7 +71,6 @@ test('mounts correctly', async () => {
|
||||
|
||||
test('dropdown options changes chart query', async () => {
|
||||
get.mockImplementation((url) => {
|
||||
console.log(url)
|
||||
if (url.includes('usage')) return {}
|
||||
return { data: MOCK_CHART_DATA }
|
||||
})
|
||||
@@ -79,9 +78,8 @@ test('dropdown options changes chart query', async () => {
|
||||
await waitFor(() => screen.getByText(/Statistics for past 60 minutes/))
|
||||
await waitFor(() => screen.getAllByRole('button', { name: '60 minutes' }))
|
||||
console.log(get.mock.calls)
|
||||
await waitFor(()=>{
|
||||
await waitFor(() => {
|
||||
expect(get).toHaveBeenCalledWith(expect.stringContaining('interval=minutely'))
|
||||
|
||||
})
|
||||
// find button that has radix id
|
||||
const [btn] = screen.getAllByRole('button', { name: '60 minutes' }).filter((e) => e.id)
|
||||
@@ -93,5 +91,4 @@ test('dropdown options changes chart query', async () => {
|
||||
userEvent.click(screen.getByText(/24 hours/))
|
||||
await waitFor(() => screen.getByText(/Statistics for past 24 hours/))
|
||||
expect(get).toHaveBeenCalledWith(expect.stringContaining('interval=hourly'))
|
||||
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,14 @@
|
||||
import LogPanel from 'components/interfaces/Settings/Logs/LogPanel'
|
||||
import { render, waitFor, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { clickDropdown } from 'tests/helpers'
|
||||
|
||||
jest.mock('components/ui/Flag/Flag')
|
||||
import Flag from 'components/ui/Flag/Flag'
|
||||
Flag.mockImplementation(({ children }) => <>{children}</>)
|
||||
jest.mock('hooks')
|
||||
import { useFlag } from 'hooks'
|
||||
useFlag.mockReturnValue(true)
|
||||
|
||||
test('templates', async () => {
|
||||
const mockFn = jest.fn()
|
||||
@@ -59,3 +67,56 @@ test('reset search filter', async () => {
|
||||
expect(() => screen.getByTitle(/Clear search/)).toThrow()
|
||||
expect(() => screen.getByDisplayValue(/something123/)).toThrow()
|
||||
})
|
||||
|
||||
test('timestamp from filter default value', async () => {
|
||||
render(<LogPanel defaultFromValue="2022-01-18T10:43:39+0000" />)
|
||||
userEvent.click(await screen.findByText('Custom'))
|
||||
await screen.findByDisplayValue('2022-01-18T10:43:39+0000')
|
||||
})
|
||||
test('timestamp from filter', async () => {
|
||||
const mockFn = jest.fn()
|
||||
render(<LogPanel onSearch={mockFn} />)
|
||||
const dropdown = await screen.findByText(/Now/)
|
||||
|
||||
// click the dropdown
|
||||
userEvent.click(dropdown)
|
||||
|
||||
// TODO: use screen.findByLabelText when https://github.com/supabase/ui/issues/310 is resolved
|
||||
await screen.findByText('From')
|
||||
// display iso timestamp
|
||||
const year = new Date().getFullYear()
|
||||
const input = await screen.findByDisplayValue(RegExp(year))
|
||||
|
||||
// replace the input's value
|
||||
userEvent.clear(input)
|
||||
|
||||
// type some random input to check for error handling
|
||||
userEvent.type(input, '123456')
|
||||
await screen.findByText(/[iI]nvalid ISO 8601 timestamp/)
|
||||
userEvent.clear(input)
|
||||
await expect(screen.findByText(/[iI]nvalid ISO 8601 timestamp/)).rejects.toThrow()
|
||||
|
||||
// get time 20 mins before
|
||||
const newDate = new Date()
|
||||
newDate.setMinutes(new Date().getMinutes() - 20)
|
||||
userEvent.type(input, newDate.toISOString())
|
||||
|
||||
// input actions
|
||||
await screen.findByTitle('Reset')
|
||||
const set = await screen.findByRole('button', { name: 'Set' })
|
||||
|
||||
userEvent.click(set)
|
||||
expect(mockFn).toBeCalled()
|
||||
await screen.findByText('Custom')
|
||||
await screen.findByTitle(/Clear timestamp filter/)
|
||||
})
|
||||
|
||||
test('custom query mode hides elements', async () => {
|
||||
const { rerender } = render(<LogPanel isCustomQuery={false} />)
|
||||
await screen.findByPlaceholderText(/Search/)
|
||||
await screen.findByText('Now')
|
||||
rerender(<LogPanel isCustomQuery={true} />)
|
||||
await expect(screen.findByPlaceholderText(/Search/)).rejects.toThrow()
|
||||
await expect(screen.findByText('Now')).rejects.toThrow()
|
||||
await expect(screen.findByText('Custom')).rejects.toThrow()
|
||||
})
|
||||
@@ -60,8 +60,9 @@ LogPage.mockImplementation((props) => {
|
||||
|
||||
import { render, fireEvent, waitFor, screen, act } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { getToggleByText } from '../../helpers'
|
||||
import { clickDropdown, getToggleByText } from '../../helpers'
|
||||
import { wait } from '@testing-library/user-event/dist/utils'
|
||||
import { find } from 'lodash'
|
||||
|
||||
beforeEach(() => {
|
||||
// reset mocks between tests
|
||||
@@ -273,16 +274,26 @@ test('q= query param will populate the query input', async () => {
|
||||
expect(() => !screen.queryByDisplayValue(/someSearch/))
|
||||
})
|
||||
|
||||
test('ts= query param will set the timestamp_start param', async () => {
|
||||
test('te= query param will populate the timestamp from input', async () => {
|
||||
// get time 20 mins before
|
||||
const newDate = new Date()
|
||||
newDate.setMinutes(new Date().getMinutes() - 20)
|
||||
const isoString = newDate.toISOString()
|
||||
const unixMicro = newDate.getTime() * 1000 //microseconds
|
||||
|
||||
useRouter.mockReturnValueOnce({
|
||||
query: { ref: '123', type: 'api', ts: 123456 },
|
||||
query: { ref: '123', type: 'api', te: unixMicro },
|
||||
push: jest.fn(),
|
||||
})
|
||||
render(<LogPage />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(get).toHaveBeenCalledWith(expect.stringContaining('timestamp_start=123456'))
|
||||
expect(get).toHaveBeenCalledWith(
|
||||
expect.stringContaining(`timestamp_end=${encodeURIComponent(unixMicro)}`)
|
||||
)
|
||||
})
|
||||
userEvent.click(await screen.findByText('Custom'))
|
||||
await screen.findByDisplayValue(isoString)
|
||||
})
|
||||
test('custom sql querying', async () => {
|
||||
get.mockImplementation((url) => {
|
||||
@@ -332,10 +343,10 @@ test('custom sql querying', async () => {
|
||||
|
||||
// clicking on the row value should not show log selection panel
|
||||
userEvent.click(screen.getByText(/12345/))
|
||||
await waitFor(() => expect(() => screen.getByText(/Metadata/)).toThrow())
|
||||
await expect(screen.findByText(/Metadata/)).rejects.toThrow()
|
||||
|
||||
// should not see chronological features
|
||||
await waitFor(() => screen.queryByText(/Load older/)) //column header
|
||||
await expect(screen.findByText(/Load older/)).rejects.toThrow()
|
||||
})
|
||||
|
||||
test('load older btn will fetch older logs', async () => {
|
||||
|
||||
+4
-13
@@ -114,22 +114,13 @@ Supabase is just Postgres, which makes it compatible with a large number of tool
|
||||
|
||||
## How to use these docs
|
||||
|
||||
The documentation is divided into 3 sections.
|
||||
The documentation is divided into 2 sections.
|
||||
|
||||
<div class="container" style={{ padding: 0 }}>
|
||||
<div class="row is-multiline">
|
||||
{/* Overview */}
|
||||
<div class="col col--4">
|
||||
<Link class="card" to="/docs/architecture" style={{ height: '100%' }}>
|
||||
<div class="card__body">
|
||||
<h4>Overview</h4>
|
||||
<p>A summary of the tools and how to get started.</p>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
{/* Auth */}
|
||||
<div class="col col--4">
|
||||
<Link class="card" to="/docs/guides" style={{ height: '100%' }}>
|
||||
<div class="col col--6">
|
||||
<Link class="card" to="/docs/" style={{ height: '100%' }}>
|
||||
<div class="card__body">
|
||||
<h4>Guides</h4>
|
||||
<p>In-depth explanations for each tool.</p>
|
||||
@@ -137,7 +128,7 @@ The documentation is divided into 3 sections.
|
||||
</Link>
|
||||
</div>
|
||||
{/* Ref */}
|
||||
<div class="col col--4">
|
||||
<div class="col col--6">
|
||||
<Link class="card" to="/docs/reference/javascript/supabase-client" style={{ height: '100%' }}>
|
||||
<div class="card__body">
|
||||
<h4>Reference Docs</h4>
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
---
|
||||
id: guides
|
||||
title: Overview
|
||||
description: Common task
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
## Guides
|
||||
|
||||
### Database
|
||||
|
||||
These guides help you unleash the power of PostgreSQL, the world's most advanced open source database.
|
||||
|
||||
### Auth
|
||||
|
||||
Authenticating users isn't the most exciting job for most developers, so Supabase makes it quick and simple, while maintaining security and performance.
|
||||
|
||||
<!--
|
||||
<a className="card" href="/docs/guides/database" style={{marginBottom: 10}}>
|
||||
<div className="card__body">
|
||||
<h3>Database</h3>
|
||||
<p>These guides help you unleash the power of PostgreSQL, the world's most advanced open source database.</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a className="card" href="/docs/guides/auth" style={{marginBottom: 10}}>
|
||||
<div className="card__body">
|
||||
<h3>Auth</h3>
|
||||
<p>Authenticating users isn't the most exciting job for most developers, so Supabase makes it quick and simple, while maintaining security and performance.</p>
|
||||
</div>
|
||||
</a> -->
|
||||
+31
-3
@@ -9,7 +9,13 @@ import TabItem from '@theme/TabItem';
|
||||
|
||||
## Overview
|
||||
|
||||
Supabase generates APIs directly from your database schema. The API is:
|
||||
Supabase generates three types of API directly from your database schema.
|
||||
|
||||
- REST - interact with your database through a restful interface.
|
||||
- Realtime - listen to database changes.
|
||||
- GraphQL - [coming soon](https://supabase.com/blog/2021/12/03/pg-graphql).
|
||||
|
||||
The APIs are:
|
||||
|
||||
- Instant and auto-generated: as you update your database the changes are immediately accessible through your API.
|
||||
- Self documenting: Supabase generates documentation in the Dashboard which updates as you make database changes.
|
||||
@@ -32,7 +38,7 @@ It provides everything you need from a CRUD API:
|
||||
<iframe
|
||||
className="w-full video-with-border"
|
||||
width="640"
|
||||
height="385"
|
||||
height="480"
|
||||
src="https://www.youtube-nocookie.com/embed/rPAJJFdtPw0"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
@@ -44,6 +50,11 @@ It provides everything you need from a CRUD API:
|
||||
Supabase provides a Realtime API using [Realtime](https://github.com/supabase/realtime). You can use this to listen to database changes over websockets.
|
||||
Realtime leverages PostgreSQL's built-in logical replication. You can manage your Realtime API simply by managing Postgres publications.
|
||||
|
||||
### GraphQL API
|
||||
|
||||
[Coming soon](https://supabase.com/blog/2021/12/03/pg-graphql). We are developing `pg_graphql`, an open source PostgreSQL extension for GraphQL.
|
||||
`pg_graphql` inspects an existing PostgreSQL schema and reflects a GraphQL schema with resolvers. We will expose `pg_graphql` via the existing REST interface.
|
||||
|
||||
|
||||
## Getting started
|
||||
|
||||
@@ -205,6 +216,23 @@ curl '[SUPABASE_URL]/rest/v1/todos' \
|
||||
[delete()](/docs/reference/javascript/delete),
|
||||
[rpc()](/docs/reference/javascript/rpc) (call Postgres functions).
|
||||
|
||||
### Safeguards towards accidental deletes and updates
|
||||
|
||||
For new projects, by default, the Postgres extension [safeupdate](https://github.com/eradman/pg-safeupdate) is enabled for all queries coming from the API.
|
||||
This ensures that any `delete()` or `update()` would fail if there are no accompanying filters provided.
|
||||
This can be disabled by running the following query on the SQL editor on the dashboard:
|
||||
|
||||
```sql
|
||||
alter role "authenticator"
|
||||
set "session_preload_libraries" to default;
|
||||
```
|
||||
|
||||
To reenable it :
|
||||
```sql
|
||||
alter role "authenticator"
|
||||
set "session_preload_libraries" = 'safeupdate';
|
||||
```
|
||||
The above can also be run to enable it for existing projects. Settings are applied immediately and as such no restarts are needed after running any of the two.
|
||||
|
||||
### Managing Realtime
|
||||
|
||||
@@ -245,7 +273,7 @@ alter publication supabase_realtime add table products;
|
||||
|
||||
#### Details and links:
|
||||
|
||||
- You should only turn on realtime for Public tables (where all data should be accessible).
|
||||
- Realtime only works for tables in the public schema.
|
||||
- [JS Reference](/docs/reference/javascript/subscribe): Subscribe to database changes using the realtime client
|
||||
|
||||
|
||||
|
||||
+70
-44
@@ -12,38 +12,65 @@ import providers from '@site/src/data/authProviders'
|
||||
<iframe
|
||||
className="w-full video-with-border"
|
||||
width="640"
|
||||
height="385"
|
||||
height="480"
|
||||
src="https://www.youtube-nocookie.com/embed/6ow_jW4epf8"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
|
||||
## User Management
|
||||
## Overview
|
||||
|
||||
Supabase makes it simple to manage your users.
|
||||
There are two parts to every Auth system:
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source src="/docs/videos/auth-zoom2.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
- **Authentication:** should this person be allowed in? If yes, who are they?
|
||||
- **Authorization:** once they are in, what are they allowed to do?
|
||||
|
||||
When users sign up, Supabase assigns them a unique ID. You can reference this ID anywhere in your database. For example, you might create a `profiles` table referencing `id` in the `auth.users` table using a `user_id` field.
|
||||
Supabase Auth is designed to work either as a standalone product, or deeply integrated with the other Supabase products.
|
||||
Postgres is at the heart of everything we do, and the Auth system follows this principle. We leverage Postgres' build-in Auth functionality wherever possible.
|
||||
|
||||
Supabase provides the routes to [sign up](/docs/reference/javascript/auth-signup), [log in](/docs/reference/javascript/auth-signin),
|
||||
[log out](/docs/reference/javascript/auth-signout), and manage users in your apps and websites.
|
||||
## Authentication
|
||||
|
||||
## Third Party Logins
|
||||
You can authenticate your users in several ways:
|
||||
|
||||
We currently support the following OAuth providers:
|
||||
- Email & password.
|
||||
- Magic links (one-click logins).
|
||||
- Social providers.
|
||||
- Phone logins.
|
||||
|
||||
<div class="container" style={{}}>
|
||||
### Providers
|
||||
|
||||
We provide a suite of Providers and login methods.
|
||||
|
||||
<div class="container" style={{ padding: 0 }}>
|
||||
<div class="row is-multiline">
|
||||
{providers.map((x) => (
|
||||
<div key={x.name} class="col col--4">
|
||||
<div key={x.name} class="col col--6">
|
||||
<Link class="card" to={x.href}>
|
||||
<div class="card__body" style={{ display: 'flex', gap: 20 }}>
|
||||
{x.logo && <img src={x.logo} alt={x.name} width="20" />}
|
||||
<p>{x.name}</p>
|
||||
<div class="card__body">
|
||||
<div class="" style={{ display: 'flex', justifyContent: 'space-between', gap: 10 }}>
|
||||
{x.logo && <img src={x.logo} alt={x.name} width="20" />}
|
||||
<p>{x.name}</p>
|
||||
<p>
|
||||
{x.official ? <span class={`badge badge--official`}>
|
||||
Official
|
||||
</span>:
|
||||
<span class={`badge badge--unofficial`}>
|
||||
Unofficial
|
||||
</span>
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
|
||||
<div class="code-block" style={{ width: '100%', display: 'flex', justifyContent: 'space-between', fontSize: '0.7rem' }}>
|
||||
<span>Platform:</span>
|
||||
<span>{x.platform.toString()}</span>
|
||||
</div>
|
||||
<div class="code-block" style={{ width: '100%', display: 'flex', justifyContent: 'space-between', fontSize: '0.7rem' }}>
|
||||
<span>Self-Hosted:</span>
|
||||
<span>{x.selfHosted.toString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
@@ -51,11 +78,25 @@ We currently support the following OAuth providers:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
You can enable providers by navigating to Authentication > Settings > External OAuth Providers and inputting your `Client ID` and `Secret` for each.
|
||||
|
||||
### Simple interface
|
||||
|
||||
You can enable third-providers with the click of a button by navigating to Authentication > Settings > External OAuth Providers and inputting your `Client ID` and `Secret` for each.
|
||||
|
||||

|
||||
|
||||
## Row Level Security
|
||||
|
||||
## Authorization
|
||||
|
||||
|
||||
When you need granular authorization rules, nothing beats PostgreSQL's Row Level Security (RLS).
|
||||
|
||||
Policies are PostgreSQL's rule engine. They are incredibly powerful and flexible, allowing you to write complex SQL rules which fit your unique business needs.
|
||||
|
||||
Get started with our [Row Level Security Guides](/docs/guides/auth/row-level-security).
|
||||
|
||||
|
||||
### Row Level Security
|
||||
|
||||
Authentication only gets you so far. When you need granular authorization rules, nothing beats PostgreSQL's [Row Level Security (RLS)](https://www.postgresql.org/docs/current/ddl-rowsecurity.html). Supabase makes it simple to turn RLS on and off.
|
||||
|
||||
@@ -63,7 +104,7 @@ Authentication only gets you so far. When you need granular authorization rules,
|
||||
<source src="/docs/videos/rls-zoom2.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
## Policies
|
||||
### Policies
|
||||
|
||||
[Policies](https://www.postgresql.org/docs/current/sql-createpolicy.html) are PostgreSQL's rule engine. They are incredibly powerful and flexible, allowing you to write complex SQL rules which fit your unique business needs.
|
||||
|
||||
@@ -94,7 +135,7 @@ let user = await supabase.from('users').select('user_id, name')
|
||||
```
|
||||
|
||||
|
||||
## How It Works
|
||||
### How It Works
|
||||
|
||||
1. A user signs up. Supabase creates a new user in the `auth.users` table.
|
||||
2. Supabase returns a new JWT, which contains the user's `UUID`.
|
||||
@@ -104,37 +145,22 @@ let user = await supabase.from('users').select('user_id, name')
|
||||
|
||||
Supabase provides a special function in Postgres, `auth.uid()`, which extracts the user's UID from the JWT. This is especially useful when creating policies.
|
||||
|
||||
## Tips
|
||||
|
||||
#### Enable Realtime for database tables
|
||||
|
||||
Realtime server broadcasts database changes to authorized users depending on your Row Level Security (RLS) policies.
|
||||
We recommend that you enable row level security and set row security policies on tables that you add to the publication.
|
||||
However, you may choose to disable RLS on a table and have changes broadcast to all connected clients.
|
||||
|
||||
|
||||
```sql
|
||||
/**
|
||||
* REALTIME SUBSCRIPTIONS
|
||||
* Realtime enables listening to any table in your public schema.
|
||||
*/
|
||||
## User Management
|
||||
|
||||
begin;
|
||||
-- remove the realtime publication
|
||||
drop publication if exists supabase_realtime;
|
||||
Supabase makes it simple to manage your users.
|
||||
|
||||
-- re-create the publication but don't enable it for any tables
|
||||
create publication supabase_realtime;
|
||||
commit;
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source src="/docs/videos/auth-zoom2.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
-- add a table to the publication
|
||||
alter publication supabase_realtime add table products;
|
||||
When users sign up, Supabase assigns them a unique ID. You can reference this ID anywhere in your database. For example, you might create a `profiles` table referencing `id` in the `auth.users` table using a `user_id` field.
|
||||
|
||||
Supabase provides the routes to [sign up](/docs/reference/javascript/auth-signup), [log in](/docs/reference/javascript/auth-signin),
|
||||
[log out](/docs/reference/javascript/auth-signout), and manage users in your apps and websites.
|
||||
|
||||
-- add other tables to the publication
|
||||
alter publication supabase_realtime add table posts;
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Read more about Auth in the [Guides](/docs/guides/auth/intro).
|
||||
- Sign in: [app.supabase.io](https://app.supabase.io)
|
||||
@@ -27,7 +27,7 @@ Apple OAuth consists of six broad steps:
|
||||
- Go to [developer.apple.com](https://developer.apple.com).
|
||||
- Click on `Account` at the top right to log in.
|
||||
|
||||

|
||||

|
||||
|
||||
### Obtain an App ID
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Azure OAuth consists of six broad steps:
|
||||
- The redirect / callback URI should look like this: `https://<project-ref>.supabase.co/auth/v1/callback`
|
||||
- Click "Register" at the bottom of the form.
|
||||
|
||||

|
||||

|
||||
|
||||
### Obtain a Client ID
|
||||
|
||||
@@ -42,7 +42,7 @@ This will serve as the `client_id` when you make API calls to authenticate the u
|
||||
|
||||
- Once your app has been registered, the client id can be found under the [list of app registrations](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps) under the column titled "Application (client) ID".
|
||||
|
||||

|
||||

|
||||
|
||||
### Obtain a Secret ID
|
||||
|
||||
@@ -54,7 +54,7 @@ This will serve as the `client_secret` when you make API calls to authenticate t
|
||||
- Enter a description and choose your preferred expiry for the secret.
|
||||
- Once the secret is generated, save the value (not the secret ID).
|
||||
|
||||

|
||||

|
||||
|
||||
### Add login code to your client app
|
||||
|
||||
@@ -62,7 +62,7 @@ The JavaScript client code is documented in the [Supabase OAuth Reference](/docs
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'Azure',
|
||||
provider: 'azure',
|
||||
})
|
||||
```
|
||||
|
||||
@@ -71,7 +71,7 @@ Add a function which you can call from a button, link, or UI element.
|
||||
```js
|
||||
async function signInWithAzure() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'Azure',
|
||||
provider: 'azure',
|
||||
}, {
|
||||
scopes: 'email',
|
||||
})
|
||||
|
||||
@@ -24,7 +24,7 @@ Setting up Bitbucket logins for your application consists of 3 parts:
|
||||
- Go to [bitbucket.org](https://bitbucket.org/).
|
||||
- Click on `Login` at the top right to log in.
|
||||
|
||||

|
||||

|
||||
|
||||
### Find your callback URL
|
||||
|
||||
|
||||
@@ -24,11 +24,11 @@ Setting up Discord logins for your application consists of 3 parts:
|
||||
- Go to [discord.com](https://discord.com/).
|
||||
- Click on `Login` at the top right to log in.
|
||||
|
||||

|
||||

|
||||
|
||||
- Once logged in, go to [discord.com/developers](https://discord.com/developers).
|
||||
|
||||

|
||||

|
||||
|
||||
### Find your callback URL
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Setting up Facebook logins for your application consists of 3 parts:
|
||||
- Go to [developers.facebook.com](https://developers.facebook.com).
|
||||
- Click on `Log In` at the top right to log in.
|
||||
|
||||

|
||||

|
||||
|
||||
### Create a Facebook App
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Setting up GitHub logins for your application consists of 3 parts:
|
||||
- Go to [github.com](https://github.com).
|
||||
- Click on `Sign In` at the top right to log in.
|
||||
|
||||

|
||||

|
||||
|
||||
### Create a GitHub Oauth App
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Setting up GitLab logins for your application consists of 3 parts:
|
||||
- Go to [gitlab.com](https://gitlab.com).
|
||||
- Click on `Login` at the top right to log in.
|
||||
|
||||

|
||||

|
||||
|
||||
### Find your callback URL
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Setting up Google logins for your application consists of 3 parts:
|
||||
- Go to [cloud.google.com](https://cloud.google.com).
|
||||
- Click on `Sign in` at the top right to log in.
|
||||
|
||||

|
||||

|
||||
|
||||
### Create a Google Cloud Platform Project
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Setting up Slack logins for your application consists of 3 parts:
|
||||
- Go to [api.slack.com](https://api.slack.com/apps).
|
||||
- Click on `Your Apps` at the top right to log in.
|
||||
|
||||

|
||||

|
||||
|
||||
### Find your callback URL
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Setting up Spotify logins for your application consists of 3 parts:
|
||||
- Log into [Spotify](https://spotify.com)
|
||||
- Access the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard)
|
||||
|
||||

|
||||

|
||||
|
||||
### Find your callback URL
|
||||
|
||||
|
||||
@@ -25,11 +25,11 @@ Setting up Twitch logins for your application consists of 3 parts:
|
||||
- Click on `Log in with Twitch` at the top right to log in.
|
||||
- If you have not already enabled 2-Factor Authentication for your Twitch Account, you will need to do that at [Twitch Security Settings](https://www.twitch.tv/settings/security) before you can continue.
|
||||
|
||||

|
||||

|
||||
|
||||
- Once logged in, go to the [Twitch Developer Console](https://dev.twitch.tv/console).
|
||||
|
||||

|
||||

|
||||
|
||||
### Find your callback URL
|
||||
|
||||
@@ -49,11 +49,11 @@ In the next step you require a callback URL, which looks like this:
|
||||
|
||||
### Create a Twitch Application
|
||||
|
||||

|
||||

|
||||
|
||||
- Click on `+ Register Your Application` at the top right.
|
||||
|
||||

|
||||

|
||||
|
||||
- Enter the name of your application.
|
||||
- Type or paste your `OAuth Redirect URL` (the callback URL from the previous step.)
|
||||
@@ -64,13 +64,13 @@ In the next step you require a callback URL, which looks like this:
|
||||
|
||||
- Click `Manage` at the right of your application entry in the list.
|
||||
|
||||

|
||||

|
||||
|
||||
- Copy your Client ID.
|
||||
- Click `New Secret` to create a new Client Secret.
|
||||
- Copy your Client Secret.
|
||||
|
||||

|
||||

|
||||
|
||||
### Add your Twitch credentials into your Supabase Project
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Setting up Twitter logins for your application consists of 3 parts:
|
||||
- Go to [developer.twitter.com](https://developer.twitter.com).
|
||||
- Click on `Sign in` at the top right to log in.
|
||||
|
||||

|
||||

|
||||
|
||||
### Find your callback URL
|
||||
|
||||
|
||||
@@ -4,9 +4,22 @@ title: Database
|
||||
description: Use Supabase to manage your data.
|
||||
---
|
||||
|
||||
import ExtensionsComponent from '@site/src/components/Extensions'
|
||||
|
||||
Supabase is built on top of [Postgres](/docs/postgres/server/about), an extremely scalable Relational Database.
|
||||
Every Supabase project comes with a full [Postgres](https://www.postgresql.org/) database, a free and open source
|
||||
database which is considered one of the world's most stable and advanced databases.
|
||||
|
||||
|
||||
## Postgres or PostgreSQL?
|
||||
|
||||
PostgreSQL the database was derived from the POSTGRES Project, a package written at the University of California at Berkeley in 1986.
|
||||
This package included a query language called "PostQUEL".
|
||||
|
||||
In 1994, Postgres95 was built on top of POSTGRES code, adding an SQL language interpreter as a replacement for PostQUEL.
|
||||
Eventually, Postgres95 was renamed to PostgreSQL to reflect the SQL query capability.
|
||||
|
||||
After this, many people referred to it as Postgres since it's less prone to confusion. Supabase is all about
|
||||
simplicity, so we also refer to it as Postgres.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ At it's most basic a function has the following parts:
|
||||
3. `language sql`: The language used inside the function body. This can also be a procedural language: `plpgsql`, `plv8`, `plpython`, etc.
|
||||
4. `as $$`: The function wrapper. Anything enclosed inside the `$$` symbols will be part of the function body.
|
||||
5. `select 'hello world';`: A simple function body. The final `select` statement inside a function body will be returned if there are no statements following it.
|
||||
6. `$$;`: The closing symbols of the funciton wrapper.
|
||||
6. `$$;`: The closing symbols of the function wrapper.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
id: introduction
|
||||
title: "Introduction"
|
||||
slug: introduction
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
Every Supabase project comes with a full Postgres database, a free and open source
|
||||
database which is considered one of the world's most stable and advanced databases.
|
||||
|
||||
|
||||
## Postgres or PostgreSQL?
|
||||
|
||||
|
||||
The database was derived from the POSTGRES Project, a package written at the University of California at Berkeley in 1986.
|
||||
This package included a query language called "PostQUEL".
|
||||
|
||||
In 1994, Postgres95 was built on top of POSTGRES code, adding an SQL language interpreter as a replacement for PostQUEL.
|
||||
Eventually, Postgres95 was renamed to PostgreSQL to reflect the SQL query capability.
|
||||
|
||||
After this, many people referred to it as Postgres since it's less prone to confusion. Supabase is all about
|
||||
simplicity, so we also refer to it as Postgres.
|
||||
|
||||
<!--
|
||||
|
||||
## Relational
|
||||
|
||||
Postgres is a Relational Database.
|
||||
|
||||
## SQL
|
||||
|
||||
|
||||
Postgres is a SQL database.
|
||||
|
||||
-->
|
||||
@@ -6,7 +6,55 @@ description: 'Examples you can use to get started with Supabase'
|
||||
|
||||
We have a [set of examples](https://github.com/supabase/supabase/tree/master/examples) in our [main repository](https://github.com/supabase/supabase) to help you get started.
|
||||
|
||||
## Todo List
|
||||
## Featured
|
||||
|
||||
|
||||
### Supabase Crash Course
|
||||
|
||||
By [Traversy Media](https://www.youtube.com/watch?v=7uKQBl9uZ00).
|
||||
|
||||
<iframe
|
||||
className="w-full video-with-border"
|
||||
width="640"
|
||||
height="480"
|
||||
src="https://www.youtube-nocookie.com/embed/7uKQBl9uZ00"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
|
||||
### Build an App With Supabase and NextJS
|
||||
|
||||
By [@jlengstorf](https://twitter.com/jlengstorf) and [@jonmeyers_io](https://twitter.com/jonmeyers_io).
|
||||
|
||||
<iframe
|
||||
className="w-full video-with-border"
|
||||
width="640"
|
||||
height="480"
|
||||
src="https://www.youtube-nocookie.com/embed/8vqY1KT4TLU"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
|
||||
|
||||
### Is Supabase Legit
|
||||
|
||||
By [Fireship](https://www.youtube.com/watch?v=WiwfiVdfRIc).
|
||||
|
||||
<iframe
|
||||
className="w-full video-with-border"
|
||||
width="640"
|
||||
height="480"
|
||||
src="https://www.youtube-nocookie.com/embed/WiwfiVdfRIc"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
|
||||
## Official Examples
|
||||
|
||||
### Todo List
|
||||
|
||||
Build a basic Todo List with Supabase and your favorite frontend framework:
|
||||
|
||||
@@ -17,13 +65,13 @@ Build a basic Todo List with Supabase and your favorite frontend framework:
|
||||
- [Vue 3 Todo List (Typescript).](https://github.com/supabase/supabase/tree/master/examples/vue3-ts-todo-list)
|
||||
- [Angular Todo List.](https://github.com/supabase/supabase/tree/master/examples/angular-todo-list)
|
||||
|
||||
## Auth examples
|
||||
### Auth examples
|
||||
|
||||
- [Supabase Auth with vanilla Javascript.](https://github.com/supabase/supabase/tree/master/examples/javascript-auth). Use Supabase without any frontend frameworks.
|
||||
- [Supabase Auth with Next.js SSR.](https://github.com/supabase/supabase/tree/master/examples/nextjs-with-supabase-auth) Uses cookies to persist auth between the server and the client.
|
||||
- [Supabase Auth with RedwoodJS.](https://redwood-playground-auth.netlify.app/supabase) Try out Supabase authentication in the [RedwoodJS](https://redwoodjs.com) Authentication Playground complete with OAuth support and code samples.
|
||||
|
||||
## Collaborative
|
||||
### Collaborative
|
||||
|
||||
- [Next.js Slack Clone.](https://github.com/supabase/supabase/tree/master/examples/nextjs-slack-clone)
|
||||
|
||||
@@ -31,6 +79,7 @@ Build a basic Todo List with Supabase and your favorite frontend framework:
|
||||
|
||||
### Libraries
|
||||
|
||||
- D (in development): [GitHub](https://github.com/csharpdf/dupabase)
|
||||
- Dart (in development): [GitHub](https://github.com/supabase/supabase-dart)
|
||||
- Python (in development): [GitHub](https://github.com/supabase/supabase-py)
|
||||
- C# (in development): [GitHub](https://github.com/supabase/supabase-csharp)
|
||||
|
||||
@@ -26,8 +26,8 @@ You can invite your Team members into your Organizations so that you can collabo
|
||||
Each project on Supabase comes with these features:
|
||||
|
||||
- A dedicated Postgres database. [Learn more](/docs/guides/database)
|
||||
- Auto-generated APIs.
|
||||
- User management. [Learn more](/docs/guides/auth)
|
||||
- Auto-generated APIs. [Learn more](/docs/guides/api)
|
||||
- Auth and User management. [Learn more](/docs/guides/auth)
|
||||
- Storage. [Learn more](/docs/guides/storage)
|
||||
|
||||
## Next Steps
|
||||
|
||||
@@ -9,7 +9,9 @@ import TabItem from '@theme/TabItem';
|
||||
|
||||
Supabase provides a CLI so that you can develop your application locally, rather than connecting to a live project.
|
||||
|
||||
## Prerequisites
|
||||
## Getting started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
You will need to have these in your environment:
|
||||
|
||||
@@ -17,7 +19,13 @@ You will need to have these in your environment:
|
||||
- Docker (make sure the daemon is up and running)
|
||||
- Supabase CLI (instructions [here](https://github.com/supabase/cli))
|
||||
|
||||
## Initialize your project
|
||||
### Initialize git
|
||||
|
||||
```bash
|
||||
git init
|
||||
```
|
||||
|
||||
### Initialize your project
|
||||
|
||||
```bash
|
||||
supabase init
|
||||
@@ -25,7 +33,7 @@ supabase init
|
||||
|
||||
This command will create a `supabase` folder which holds all the configuration for developing your project locally.
|
||||
|
||||
## Start
|
||||
### Start
|
||||
|
||||
```bash
|
||||
supabase start
|
||||
@@ -45,7 +53,7 @@ Started local development setup.
|
||||
service_role key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIn0.M2d2z4SFn5C7HlJlaSLfrzuYim9nbY_XI40uWFN3hEE
|
||||
```
|
||||
|
||||
#### Accessing Services Directly
|
||||
### Accessing Services Directly
|
||||
|
||||
<Tabs
|
||||
defaultValue="Postgres"
|
||||
@@ -95,7 +103,7 @@ http://localhost:54321/auth/v1/ # Auth (GoTrue)
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Example application
|
||||
### Example application
|
||||
|
||||
Now that we've learned how to install and start Supabase locally, let's see how you can use it with a frontend application.
|
||||
This quick-start guide will show you, how you can configure a React app which uses the Supabase stack.
|
||||
@@ -139,7 +147,7 @@ npm start # Start the React app
|
||||
|
||||
If everything is working, you should have a React app running on `http://localhost:3000` and Supabase services running on `http://localhost:54321`
|
||||
|
||||
## Stop
|
||||
### Stop
|
||||
|
||||
When you're finished with Supabase, send <kbd>Ctrl-C</kbd> to stop the Docker services.
|
||||
|
||||
|
||||
@@ -349,13 +349,6 @@ on storage.objects for select
|
||||
using ( auth.uid() = owner );
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
### Go Easy
|
||||
|
||||
Supabase Storage is in Beta. If you're experiencing any issues,
|
||||
let us know on our [GitHub](https://github.com/supabase/supabase/discussions) and we will fix it as fast as we can.
|
||||
|
||||
## Next steps
|
||||
|
||||
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
|
||||
|
||||
@@ -0,0 +1,519 @@
|
||||
---
|
||||
id: with-expo
|
||||
title: "Quickstart: Expo"
|
||||
description: Learn how to use Supabase in your React Native App.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
## Intro
|
||||
|
||||
This example provides the steps to build a simple user management app (from scratch!) using Supabase and React Native. It includes:
|
||||
|
||||
- Supabase [Database](/docs/guides/database): a Postgres database for storing your user data.
|
||||
- Supabase [Auth](/docs/guides/auth): users can sign in with magic links (no passwords, only email).
|
||||
- Supabase [Storage](/docs/guides/storage): users can upload a photo.
|
||||
- [Row Level Security](/docs/guides/auth#row-level-security): data is protected so that individuals can only access their own data.
|
||||
- Instant [APIs](/docs/guides/api): APIs will be automatically generated when you create your database tables.
|
||||
|
||||
By the end of this guide you'll have an app which allows users to login and update some basic profile details:
|
||||
|
||||
## Project set up
|
||||
|
||||
Before we start building we're going to set up our Database and API. This is as simple as starting a new Project in Supabase
|
||||
and then creating a "schema" inside the database.
|
||||
|
||||
### Create a project
|
||||
|
||||
1. Go to [app.supabase.io](https://app.supabase.io).
|
||||
1. Click on "New Project".
|
||||
1. Enter your project details.
|
||||
1. Wait for the new database to launch.
|
||||
|
||||
|
||||
### Set up the database schema
|
||||
|
||||
Now we are going to set up the database schema. We can use the "User Management Starter" quickstart in the SQL Editor,
|
||||
or you can just copy/paste the SQL from below and run it yourself.
|
||||
|
||||
<Tabs
|
||||
defaultValue="UI"
|
||||
values={[
|
||||
{label: 'UI', value: 'UI'},
|
||||
{label: 'SQL', value: 'SQL'},
|
||||
]}>
|
||||
<TabItem value="UI">
|
||||
|
||||
```sh
|
||||
1. Go to the "SQL" section.
|
||||
2. Click "User Management Starter".
|
||||
3. Click "Run".
|
||||
```
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source src="/docs/videos/sql-user-management-starter.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="SQL">
|
||||
|
||||
```sql
|
||||
-- Create a table for public "profiles"
|
||||
create table profiles (
|
||||
id uuid references auth.users not null,
|
||||
updated_at timestamp with time zone,
|
||||
username text unique,
|
||||
avatar_url text,
|
||||
website text,
|
||||
|
||||
primary key (id),
|
||||
unique(username),
|
||||
constraint username_length check (char_length(username) >= 3)
|
||||
);
|
||||
|
||||
alter table profiles enable row level security;
|
||||
|
||||
create policy "Public profiles are viewable by everyone."
|
||||
on profiles for select
|
||||
using ( true );
|
||||
|
||||
create policy "Users can insert their own profile."
|
||||
on profiles for insert
|
||||
with check ( auth.uid() = id );
|
||||
|
||||
create policy "Users can update own profile."
|
||||
on profiles for update
|
||||
using ( auth.uid() = id );
|
||||
|
||||
-- Set up Realtime!
|
||||
begin;
|
||||
drop publication if exists supabase_realtime;
|
||||
create publication supabase_realtime;
|
||||
commit;
|
||||
alter publication supabase_realtime add table profiles;
|
||||
|
||||
-- Set up Storage!
|
||||
insert into storage.buckets (id, name)
|
||||
values ('avatars', 'avatars');
|
||||
|
||||
create policy "Avatar images are publicly accessible."
|
||||
on storage.objects for select
|
||||
using ( bucket_id = 'avatars' );
|
||||
|
||||
create policy "Anyone can upload an avatar."
|
||||
on storage.objects for insert
|
||||
with check ( bucket_id = 'avatars' );
|
||||
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
### Get the API Keys
|
||||
|
||||
|
||||
Now that you've created some database tables, you are ready to insert data using the auto-generated API.
|
||||
We just need to get the URL and `anon` key from the API settings.
|
||||
|
||||
<Tabs
|
||||
defaultValue="UI"
|
||||
values={[
|
||||
{label: 'UI', value: 'UI'}
|
||||
]}>
|
||||
<TabItem value="UI">
|
||||
|
||||
```sh
|
||||
1. Go to the "Settings" section.
|
||||
2. Click "API" in the sidebar.
|
||||
3. Find your API URL in this page.
|
||||
4. Find your "anon" and "service_role" keys on this page.
|
||||
```
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source src="/docs/videos/api/api-url-and-key.mp4" type="video/mp4" muted playsInline />
|
||||
</video>
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Building the App
|
||||
|
||||
Let's start building the React Native app from scratch.
|
||||
|
||||
### Initialize a React Native app
|
||||
|
||||
We can use [`expo`](https://docs.expo.dev/get-started/create-a-new-app/) to initialize
|
||||
an app called `supabaseReactNative`:
|
||||
|
||||
```bash
|
||||
#select the "Blank" template when prompted
|
||||
expo init supabaseReactNative
|
||||
|
||||
cd supabaseReactNative
|
||||
```
|
||||
|
||||
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)
|
||||
|
||||
```bash
|
||||
yarn add @supabase/supabase-js
|
||||
```
|
||||
|
||||
Now let's create a helper file to initialize the Supabase client.
|
||||
We need the API URL and the `anon` key that you copied [earlier](#get-the-api-keys).
|
||||
These variables will be exposed on the browser, and that's completely fine since we have
|
||||
[Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
|
||||
|
||||
```js title="lib/supabase.js"
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabaseUrl = YOUR_REACT_NATIVE_SUPABASE_URL
|
||||
const supabaseAnonKey = YOUR_REACT_NATIVE_SUPABASE_ANON_KEY
|
||||
|
||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
|
||||
```
|
||||
|
||||
### Set up a Login component
|
||||
|
||||
Let's set up a React Native component to manage logins and sign ups.
|
||||
We'll use Magic Links, so users can sign in with their email without using passwords.
|
||||
|
||||
```jsx title="components/Auth.js"
|
||||
import React, {useState} from 'react'
|
||||
import {Alert, StyleSheet, View} from 'react-native'
|
||||
import {supabaseClient} from '../lib/initSupabase'
|
||||
import {Button, Input} from 'react-native-elements'
|
||||
|
||||
export default function Auth() {
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [loading, setLoading] = useState('')
|
||||
|
||||
async function signInWithEmail() {
|
||||
const { user, error } = await supabase.auth.signIn({
|
||||
email: email,
|
||||
password: password,
|
||||
})
|
||||
}
|
||||
|
||||
async function signUpWithEmail() {
|
||||
const { user, error } = await supabase.auth.signUp({
|
||||
email: email,
|
||||
password: password,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style={[styles.verticallySpaced, styles.mt20]}>
|
||||
<Input
|
||||
label="Email"
|
||||
leftIcon={{type: 'font-awesome', name: 'envelope'}}
|
||||
onChangeText={text => setEmail(text)}
|
||||
value={email}
|
||||
placeholder="email@address.com"
|
||||
autoCapitalize={'none'}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.verticallySpaced}>
|
||||
<Input
|
||||
label="Password"
|
||||
leftIcon={{type: 'font-awesome', name: 'lock'}}
|
||||
onChangeText={text => setPassword(text)}
|
||||
value={password}
|
||||
secureTextEntry={true}
|
||||
placeholder="Password"
|
||||
autoCapitalize={'none'}
|
||||
/>
|
||||
</View>
|
||||
<View style={[styles.verticallySpaced, styles.mt20]}>
|
||||
<Button
|
||||
title="Sign in"
|
||||
onPress={() => signInWithEmail(email, password)}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.verticallySpaced}>
|
||||
<Button
|
||||
title="Sign up"
|
||||
onPress={() => signUpWithEmail(email, password)}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
marginTop: 40,
|
||||
padding: 12,
|
||||
},
|
||||
verticallySpaced: {
|
||||
paddingTop: 4,
|
||||
paddingBottom: 4,
|
||||
alignSelf: 'stretch',
|
||||
},
|
||||
mt20: {
|
||||
marginTop: 20,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Account page
|
||||
|
||||
After a user is signed in we can allow them to edit their profile details and manage their account.
|
||||
|
||||
Let's create a new component for that called `Account.js`.
|
||||
|
||||
```jsx title="components/Account.js"
|
||||
import { useState, useEffect } from 'react'
|
||||
import { supabase } from '../utils/supabaseClient'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
|
||||
import {Button, Input} from 'react-native-elements'
|
||||
|
||||
export default function Account({ session }) {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [username, setUsername] = useState(null)
|
||||
const [website, setWebsite] = useState(null)
|
||||
const [avatar_url, setAvatarUrl] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
getProfile()
|
||||
}, [session])
|
||||
|
||||
async function getProfile() {
|
||||
try {
|
||||
setLoading(true)
|
||||
const user = supabase.auth.user()
|
||||
|
||||
let { data, error, status } = await supabase
|
||||
.from('profiles')
|
||||
.select(`username, website, avatar_url`)
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
|
||||
if (error && status !== 406) {
|
||||
throw error
|
||||
}
|
||||
|
||||
if (data) {
|
||||
setUsername(data.username)
|
||||
setWebsite(data.website)
|
||||
setAvatarUrl(data.avatar_url)
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function updateProfile({ username, website, avatar_url }) {
|
||||
try {
|
||||
setLoading(true)
|
||||
const user = supabase.auth.user()
|
||||
|
||||
const updates = {
|
||||
id: user.id,
|
||||
username,
|
||||
website,
|
||||
avatar_url,
|
||||
updated_at: new Date(),
|
||||
}
|
||||
|
||||
let { error } = await supabase.from('profiles').upsert(updates, {
|
||||
returning: 'minimal', // Don't return the value after inserting
|
||||
})
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="form-widget">
|
||||
<View>
|
||||
<Input id="email" type="text" value={session.user.email} disabled />
|
||||
</View>
|
||||
<View>
|
||||
<Input
|
||||
id="username"
|
||||
type="text"
|
||||
value={username || ''}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</View>
|
||||
<View>
|
||||
<label htmlFor="website">Website</label>
|
||||
<Input
|
||||
id="website"
|
||||
type="website"
|
||||
value={website || ''}
|
||||
onChange={(e) => setWebsite(e.target.value)}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<Button
|
||||
className="button block primary"
|
||||
onClick={() => updateProfile({ username, website, avatar_url })}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Loading ...' : 'Update'}
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<Button className="button block" onClick={() => supabase.auth.signOut()}>
|
||||
Sign Out
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Launch!
|
||||
|
||||
Now that we have all the components in place, let's update `app.js`:
|
||||
|
||||
```jsx title="app.js"
|
||||
import { useState, useEffect } from 'react'
|
||||
import { supabase } from './lib/supabaseClient'
|
||||
import Auth from './components/Auth'
|
||||
import Account from './components/Account'
|
||||
import View from 'react-native'
|
||||
|
||||
export default function App() {
|
||||
const [session, setSession] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
setSession(supabase.auth.session())
|
||||
|
||||
supabase.auth.onAuthStateChange((_event, session) => {
|
||||
setSession(session)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View className="container">
|
||||
{!session ? <Auth /> : <Account key={session.user.id} session={session} />}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Once that's done, run this in a terminal window:
|
||||
|
||||
```bash
|
||||
yarn start
|
||||
```
|
||||
|
||||
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
|
||||
|
||||
|
||||
## Bonus: Profile photos
|
||||
|
||||
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like
|
||||
photos and videos.
|
||||
|
||||
### Create an upload widget
|
||||
|
||||
Let's create an avatar for the user so that they can upload a profile photo.
|
||||
We can start by creating a new component:
|
||||
|
||||
```jsx title="components/Avatar.js"
|
||||
import { useEffect, useState } from 'react'
|
||||
import { supabase } from '../lib/supabase'
|
||||
import {Alert, StyleSheet, View, Image} from 'react-native'
|
||||
import {Button, Input} from 'react-native-elements'
|
||||
|
||||
export default function Avatar({ url, size, onUpload }) {
|
||||
const [avatarUrl, setAvatarUrl] = useState(null)
|
||||
const [uploading, setUploading] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (url) downloadImage(url)
|
||||
}, [url])
|
||||
|
||||
async function downloadImage(path) {
|
||||
try {
|
||||
const { data, error } = await supabase.storage.from('avatars').download(path)
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
const url = URL.createObjectURL(data)
|
||||
setAvatarUrl(url)
|
||||
} catch (error) {
|
||||
console.log('Error downloading image: ', error.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function uploadAvatar(event) {
|
||||
try {
|
||||
setUploading(true)
|
||||
|
||||
if (!event.target.files || event.target.files.length === 0) {
|
||||
throw new Error('You must select an image to upload.')
|
||||
}
|
||||
|
||||
const file = event.target.files[0]
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const fileName = `${Math.random()}.${fileExt}`
|
||||
const filePath = `${fileName}`
|
||||
|
||||
let { error: uploadError } = await supabase.storage
|
||||
.from('avatars')
|
||||
.upload(filePath, file)
|
||||
|
||||
if (uploadError) {
|
||||
throw uploadError
|
||||
}
|
||||
|
||||
onUpload(filePath)
|
||||
} catch (error) {
|
||||
alert(error.message)
|
||||
} finally {
|
||||
setUploading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
{avatarUrl ? (
|
||||
<Image
|
||||
src={avatarUrl}
|
||||
alt="Avatar"
|
||||
className="avatar image"
|
||||
/>
|
||||
) : (
|
||||
<View className="avatar no-image" />
|
||||
)}
|
||||
<View>
|
||||
<Input
|
||||
type="file"
|
||||
id="single"
|
||||
accept="image/*"
|
||||
onChange={uploadAvatar}
|
||||
disabled={uploading}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
|
||||
- Sign in: [app.supabase.io](https://app.supabase.io)
|
||||
@@ -8,13 +8,20 @@ import Sponsors from '@site/src/components/Sponsors'
|
||||
|
||||
## How to contribute
|
||||
|
||||
Supabase has many [open source repos](https://github.com/orgs/supabase/repositories). Some of the more popular ones are:
|
||||
- [Supabase](https://github.com/supabase/supabase) - Our Dashboard, Websites, and example apps
|
||||
- [Supabase-js](https://github.com/supabase/supabase-js) - A clientside library written in Typescript
|
||||
- [Gotrue](https://github.com/supabase/gotrue) - Our auth server, written in Go
|
||||
- [Realtime](https://github.com/supabase/realtime) - A server for streaming changes from Postgres, written in Elixir
|
||||
- [browse all]((https://github.com/orgs/supabase/repositories))
|
||||
|
||||
Want to contribute? Why not jump into our GitHub repo and:
|
||||
|
||||
- [Sponsor Supabase](https://github.com/sponsors/supabase).
|
||||
- [Answer Discussions](https://github.com/supabase/supabase/discussions).
|
||||
- Submit an issue.
|
||||
- Submit an issue to one of our [repos](https://github.com/orgs/supabase/repositories)
|
||||
- Report a performance issue or a part of the documentation that you find confusing.
|
||||
- Create a pull request.
|
||||
- Create a pull request in one of our [repos](https://github.com/orgs/supabase/repositories)
|
||||
- [Translate our Readme](https://github.com/supabase/supabase/issues/1341).
|
||||
- Try our products and give feedback.
|
||||
- Spread the word if you like what we are doing.
|
||||
|
||||
@@ -56,19 +56,12 @@ module.exports = {
|
||||
},
|
||||
items: [
|
||||
{
|
||||
label: 'Overview',
|
||||
label: 'Guides',
|
||||
to: '/',
|
||||
activeBaseRegex:
|
||||
'.*.docs/$|^/docs/architecture|(^/docs/guides/(database|auth|storage|api|examples!))|(^/docs/guides/(examples|with-angular|with-flutter|with-nextjs|with-react|with-redwoodjs|with-svelte|with-vue-3)|^/docs/faq|^/docs/going-into-prod|^/docs/handbook|^/docs/company)',
|
||||
position: 'left',
|
||||
},
|
||||
{
|
||||
label: 'Guides',
|
||||
to: '/docs/guides',
|
||||
activeBaseRegex:
|
||||
'(^/docs/guides/database/|^/docs/guides/auth/)|(/docs/guides$)|docs/(resources|learn)',
|
||||
position: 'left',
|
||||
},
|
||||
{
|
||||
label: 'Reference',
|
||||
to: '/docs/reference/javascript/supabase-client',
|
||||
|
||||
Generated
+3
-3
@@ -7547,9 +7547,9 @@
|
||||
"integrity": "sha512-2nMHqg1x5PU+unxX7PGY7AuYxl2qDx7PSrTRjizr8sxdd3l/3hBuWWaki62qmtYm2U5i4Z5E7GbjlyDFhs9/EQ=="
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.1.25",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz",
|
||||
"integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q=="
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz",
|
||||
"integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA=="
|
||||
},
|
||||
"nanomatch": {
|
||||
"version": "1.2.13",
|
||||
|
||||
+33
-30
@@ -43,20 +43,19 @@ module.exports = {
|
||||
items: [
|
||||
'about',
|
||||
'architecture',
|
||||
'guides/database',
|
||||
'guides/auth',
|
||||
'guides/storage',
|
||||
'guides/api',
|
||||
'guides/hosting/platform',
|
||||
'guides/local-development',
|
||||
'guides/examples',
|
||||
],
|
||||
collapsed: false,
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Tutorials',
|
||||
label: 'Quickstarts',
|
||||
collapsed: false,
|
||||
items: [
|
||||
'guides/with-angular',
|
||||
'guides/with-expo',
|
||||
'guides/with-flutter',
|
||||
'guides/with-nextjs',
|
||||
'guides/with-react',
|
||||
@@ -65,34 +64,12 @@ module.exports = {
|
||||
'guides/with-vue-3',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'See Also',
|
||||
collapsed: false,
|
||||
items: [
|
||||
'faq',
|
||||
'going-into-prod',
|
||||
'handbook/contributing',
|
||||
'handbook/supasquad',
|
||||
'company/terms',
|
||||
'company/privacy',
|
||||
'company/aup',
|
||||
],
|
||||
},
|
||||
],
|
||||
guides: [
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Guides',
|
||||
items: ['guides', 'guides/local-development'],
|
||||
collapsed: false,
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Database',
|
||||
collapsed: false,
|
||||
items: [
|
||||
'guides/database/introduction',
|
||||
'guides/database',
|
||||
'guides/database/connecting-to-postgres',
|
||||
'guides/database/tables',
|
||||
'guides/database/functions',
|
||||
@@ -125,12 +102,18 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'APIs',
|
||||
collapsed: false,
|
||||
items: ['guides/api'],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Auth',
|
||||
collapsed: false,
|
||||
items: [
|
||||
'guides/auth/intro',
|
||||
'guides/auth',
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Authentication',
|
||||
@@ -174,6 +157,12 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Storage',
|
||||
collapsed: false,
|
||||
items: ['guides/storage'],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Integrations',
|
||||
@@ -195,7 +184,21 @@ module.exports = {
|
||||
type: 'category',
|
||||
label: 'Self Hosting',
|
||||
collapsed: false,
|
||||
items: ['guides/hosting/overview', 'guides/hosting/platform', 'guides/hosting/docker'],
|
||||
items: ['guides/hosting/overview', 'guides/hosting/docker'],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'See Also',
|
||||
collapsed: false,
|
||||
items: [
|
||||
'faq',
|
||||
'going-into-prod',
|
||||
'handbook/contributing',
|
||||
'handbook/supasquad',
|
||||
'company/terms',
|
||||
'company/privacy',
|
||||
'company/aup',
|
||||
],
|
||||
},
|
||||
],
|
||||
postgresServer: [
|
||||
|
||||
+5
-4
@@ -772,6 +772,7 @@ pages:
|
||||
title: 'stream()'
|
||||
notes: |
|
||||
- `stream()` will emit the initial data as well as any further change on the database as `Stream` of `List<Map<String, dynamic>>` by combining Postgrest and Realtime.
|
||||
- Takes a list of primary key columns as its argument.
|
||||
examples:
|
||||
- name: Listening to a specific table
|
||||
isSpotlight: true
|
||||
@@ -779,7 +780,7 @@ pages:
|
||||
```dart
|
||||
supabase
|
||||
.from('countries')
|
||||
.stream()
|
||||
.stream(['id'])
|
||||
.execute();
|
||||
```
|
||||
- name: Listening to a specific rows within a table
|
||||
@@ -790,7 +791,7 @@ pages:
|
||||
```dart
|
||||
supabase
|
||||
.from('countries:id=eq.120')
|
||||
.stream()
|
||||
.stream(['id'])
|
||||
.execute();
|
||||
```
|
||||
- name: With `order()`
|
||||
@@ -798,7 +799,7 @@ pages:
|
||||
```dart
|
||||
supabase
|
||||
.from('countries')
|
||||
.stream()
|
||||
.stream(['id'])
|
||||
.order('name', ascending: false)
|
||||
.execute();
|
||||
```
|
||||
@@ -807,7 +808,7 @@ pages:
|
||||
```dart
|
||||
supabase
|
||||
.from('countries')
|
||||
.stream()
|
||||
.stream(['id'])
|
||||
.order('name', ascending: false)
|
||||
.limit(10)
|
||||
.execute();
|
||||
|
||||
@@ -21,6 +21,7 @@ Jon M
|
||||
Jonny Summers-Muir
|
||||
Joshen Lim
|
||||
Kang Ming Tay
|
||||
Lakshmipathi G
|
||||
Laura C
|
||||
Leo T
|
||||
Mark Burggraf
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -72,7 +72,7 @@ We had a crazy month during Hacktoberfest. In case you missed it, here are some
|
||||
- [@kiwicopple](https://twitter.com/kiwicopple) appeared on the [Open Sauced channel](https://www.youtube.com/watch?v=PHmiWXDx9-w) with [@bdougieYO](https://twitter.com/bdougieYO)
|
||||
- [@thorwebdev](https://twitter.com/thorwebdev) shows the [Engineers.sg](http://engineers.sg) group how to [build a realtime Slack clone](https://engineers.sg/video/building-a-slack-clone-with-authentication-and-realtime-data-syncing-using-supabase-io-singaporejs--4119) with Supabase
|
||||
|
||||
If you want to keep up to date, make sure you [subscribe to our YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ) or [follow us on Twitter](https://twitter.com/supabase).
|
||||
If you want to keep up to date, make sure you [subscribe to our YouTube channel](https://www.youtube.com/c/supabase) or [follow us on Twitter](https://twitter.com/supabase).
|
||||
|
||||
### Coming next
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ We had a crazy month during Hacktoberfest. In case you missed it, here are some
|
||||
- [@elrhomariyounes](https://github.com/elrhomariyounes) started helping [@acupofjose](https://github.com/acupofjose) with the [postgrest-csharp](https://github.com/supabase/postgrest-csharp) extension
|
||||
- [@duncanhealy](https://github.com/duncanhealy) is [updating the Redwood example](https://github.com/redwoodjs/redwood/pull/1474) to work with Supabase.js v1.0
|
||||
|
||||
If you want to keep up to date, make sure you [subscribe to our YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ) or [follow us on Twitter](https://twitter.com/supabase).
|
||||
If you want to keep up to date, make sure you [subscribe to our YouTube channel](https://www.youtube.com/c/supabase) or [follow us on Twitter](https://twitter.com/supabase).
|
||||
|
||||
### Coming next
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ We raised $6M from Y Combinator, Mozilla, and Coatue. You can read more on [Tech
|
||||
|
||||

|
||||
|
||||
If you want to keep up to date, make sure you [subscribe to our YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ) or [follow us on Twitter](https://twitter.com/supabase).
|
||||
If you want to keep up to date, make sure you [subscribe to our YouTube channel](https://www.youtube.com/c/supabase) or [follow us on Twitter](https://twitter.com/supabase).
|
||||
|
||||
### Coming next
|
||||
|
||||
|
||||
@@ -127,5 +127,5 @@ We're ramping up to "Launch week" at the end of Q1, where we will be giving you
|
||||
- Start using Supabase today: [app.supabase.io](https://app.supabase.io/)
|
||||
- Make sure to [star us on GitHub](https://github.com/supabase/supabase)
|
||||
- Follow us [on Twitter](https://twitter.com/supabase)
|
||||
- Subscribe to our [YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ)
|
||||
- Subscribe to our [YouTube channel](https://www.youtube.com/c/supabase)
|
||||
- Become a [sponsor](https://github.com/sponsors/supabase)
|
||||
@@ -107,5 +107,5 @@ Waiting for Supabase Storage? Here's a sneak peek for the upcoming Launch Week a
|
||||
- Start using Supabase today: [app.supabase.io](https://app.supabase.io/)
|
||||
- Make sure to [star us on GitHub](https://github.com/supabase/supabase)
|
||||
- Follow us [on Twitter](https://twitter.com/supabase)
|
||||
- Subscribe to our [YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ)
|
||||
- Subscribe to our [YouTube channel](https://www.youtube.com/c/supabase)
|
||||
- Become a [sponsor](https://github.com/sponsors/supabase)
|
||||
@@ -103,7 +103,7 @@ Thanks to a comunity contribution ([@\_mateomorris](https://twitter.com/_mateomo
|
||||
Source: <a href="https://repository.surf/supabase">repository.surf/supabase</a>
|
||||
</small>
|
||||
|
||||
If you want to keep up to date, make sure you [subscribe to our YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ) or [follow us on Twitter](https://twitter.com/supabase).
|
||||
If you want to keep up to date, make sure you [subscribe to our YouTube channel](https://www.youtube.com/c/supabase) or [follow us on Twitter](https://twitter.com/supabase).
|
||||
|
||||
## Coming Next
|
||||
|
||||
@@ -118,5 +118,5 @@ For this we are working on our own [Workflow engine](/blog/2021/04/02/supabase-
|
||||
- Start using Supabase today: [app.supabase.io](https://app.supabase.io/)
|
||||
- Make sure to [star us on GitHub](https://github.com/supabase/supabase)
|
||||
- Follow us [on Twitter](https://twitter.com/supabase)
|
||||
- Subscribe to our [YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ)
|
||||
- Subscribe to our [YouTube channel](https://www.youtube.com/c/supabase)
|
||||
- Become a [sponsor](https://github.com/sponsors/supabase)
|
||||
@@ -154,7 +154,7 @@ Fireship reviewed Supabase last week, and despite being a (self-proclaimed) Fire
|
||||
Source: <a href="https://repository.surf/supabase">repository.surf/supabase</a>
|
||||
</small>
|
||||
|
||||
If you want to keep up to date, make sure you [subscribe to our YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ) or [follow us on Twitter](https://twitter.com/supabase).
|
||||
If you want to keep up to date, make sure you [subscribe to our YouTube channel](https://www.youtube.com/c/supabase) or [follow us on Twitter](https://twitter.com/supabase).
|
||||
|
||||
## Coming Next
|
||||
|
||||
@@ -173,5 +173,5 @@ We're also working on our [Workflows engine](/blog/2021/04/02/supabase-workflows
|
||||
- Start using Supabase today: [app.supabase.io](https://app.supabase.io/)
|
||||
- Make sure to [star us on GitHub](https://github.com/supabase/supabase)
|
||||
- Follow us [on Twitter](https://twitter.com/supabase)
|
||||
- Subscribe to our [YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ)
|
||||
- Subscribe to our [YouTube channel](https://www.youtube.com/c/supabase)
|
||||
- Become a [sponsor](https://github.com/sponsors/supabase)
|
||||
@@ -116,7 +116,7 @@ We started a weekly 1-hour live stream where we build in public.
|
||||
Source: <a href="https://repository.surf/supabase">repository.surf/supabase</a>
|
||||
</small>
|
||||
|
||||
If you want to keep up to date, make sure you [subscribe to our YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ) or [follow us on Twitter](https://twitter.com/supabase).
|
||||
If you want to keep up to date, make sure you [subscribe to our YouTube channel](https://www.youtube.com/c/supabase) or [follow us on Twitter](https://twitter.com/supabase).
|
||||
|
||||
## Coming Next
|
||||
|
||||
@@ -127,5 +127,5 @@ We're planning another Launch Week at the end of July. We'll probably have a qui
|
||||
- Start using Supabase today: [app.supabase.io](https://app.supabase.io/)
|
||||
- Make sure to [star us on GitHub](https://github.com/supabase/supabase)
|
||||
- Follow us [on Twitter](https://twitter.com/supabase)
|
||||
- Subscribe to our [YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ)
|
||||
- Subscribe to our [YouTube channel](https://www.youtube.com/c/supabase)
|
||||
- Become a [sponsor](https://github.com/sponsors/supabase)
|
||||
@@ -136,7 +136,7 @@ We run a weekly 1-hour live stream where we build in public.
|
||||
Source: <a href="https://repository.surf/supabase">repository.surf/supabase</a>
|
||||
</small>
|
||||
|
||||
If you want to keep up to date, make sure you [subscribe to our YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ) or
|
||||
If you want to keep up to date, make sure you [subscribe to our YouTube channel](https://www.youtube.com/c/supabase) or
|
||||
[follow us on Twitter](https://twitter.com/supabase).
|
||||
|
||||
## External contributions
|
||||
@@ -167,5 +167,5 @@ Let's goooooo.
|
||||
- Start using Supabase today: [app.supabase.io](https://app.supabase.io/)
|
||||
- Make sure to [star us on GitHub](https://github.com/supabase/supabase)
|
||||
- Follow us [on Twitter](https://twitter.com/supabase)
|
||||
- Subscribe to our [YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ)
|
||||
- Subscribe to our [YouTube channel](https://www.youtube.com/c/supabase)
|
||||
- Become a [sponsor](https://github.com/sponsors/supabase)
|
||||
@@ -126,7 +126,7 @@ Get your hands on some [Supabase Swag](https://store.supabase.com/), hand packed
|
||||
Source: <a href="https://repository.surf/supabase">repository.surf/supabase</a>
|
||||
</small>
|
||||
|
||||
If you want to keep up to date, make sure you [subscribe to our YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ) or
|
||||
If you want to keep up to date, make sure you [subscribe to our YouTube channel](https://www.youtube.com/c/supabase) or
|
||||
[follow us on Twitter](https://twitter.com/supabase).
|
||||
|
||||
## Coming Next
|
||||
@@ -138,5 +138,5 @@ Security, stability, performance ... and Functions.
|
||||
- Start using Supabase today: [app.supabase.io](https://app.supabase.io/)
|
||||
- Make sure to [star us on GitHub](https://github.com/supabase/supabase)
|
||||
- Follow us [on Twitter](https://twitter.com/supabase)
|
||||
- Subscribe to our [YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ)
|
||||
- Subscribe to our [YouTube channel](https://www.youtube.com/c/supabase)
|
||||
- Become a [sponsor](https://github.com/sponsors/supabase)
|
||||
@@ -87,7 +87,7 @@ We now provide a handy copy utility for various database connection strings beca
|
||||
## We released a primer on Row Level Security
|
||||
|
||||
RLS can be a bit foreign for developers getting started with Postgres.
|
||||
This video by [@\_dijonmusters](https://twitter.com/_dijonmusters) demystifies it. If you find the video a useful medium for learning, consider [subscribing to our channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ).
|
||||
This video by [@\_dijonmusters](https://twitter.com/_dijonmusters) demystifies it. If you find the video a useful medium for learning, consider [subscribing to our channel](https://www.youtube.com/c/supabase).
|
||||
|
||||
<iframe
|
||||
className="w-full video-with-border"
|
||||
@@ -128,7 +128,7 @@ We hit 18,000 stars on GitHub, and got to the [top of GitHub trending](https://t
|
||||
Source: <a href="https://repository.surf/supabase">repository.surf/supabase</a>
|
||||
</small>
|
||||
|
||||
If you want to keep up to date, make sure you [subscribe to our YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ) or
|
||||
If you want to keep up to date, make sure you [subscribe to our YouTube channel](https://www.youtube.com/c/supabase) or
|
||||
[follow us on Twitter](https://twitter.com/supabase).
|
||||
|
||||
## Dependency contributions
|
||||
@@ -161,5 +161,5 @@ By the time we're done, Supabase will be production-ready for all use cases.
|
||||
- Start using Supabase today: [app.supabase.io](https://app.supabase.io/)
|
||||
- Make sure to [star us on GitHub](https://github.com/supabase/supabase)
|
||||
- Follow us [on Twitter](https://twitter.com/supabase)
|
||||
- Subscribe to our [YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ)
|
||||
- Subscribe to our [YouTube channel](https://www.youtube.com/c/supabase)
|
||||
- Become a [sponsor](https://github.com/sponsors/supabase)
|
||||
@@ -127,5 +127,5 @@ We're warming up for another Launch Week! Last time was "[Launch Week II: the SQ
|
||||
- Start using Supabase today: **[app.supabase.io](https://app.supabase.io/)**
|
||||
- Make sure to **[star us on GitHub](https://github.com/supabase/supabase)**
|
||||
- Follow us **[on Twitter](https://twitter.com/supabase)**
|
||||
- Subscribe to our **[YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ)**
|
||||
- Subscribe to our **[YouTube channel](https://www.youtube.com/c/supabase)**
|
||||
- Become a **[sponsor](https://github.com/sponsors/supabase)**
|
||||
@@ -266,5 +266,5 @@ If you want to help us build the future of cloud-native Postgres, join us:
|
||||
- Start using Supabase today: **[app.supabase.io](https://app.supabase.io/)**
|
||||
- Make sure to **[star us on GitHub](https://github.com/supabase/supabase)**
|
||||
- Follow us **[on Twitter](https://twitter.com/supabase)**
|
||||
- Subscribe to our **[YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ)**
|
||||
- Subscribe to our **[YouTube channel](https://www.youtube.com/c/supabase)**
|
||||
- Become a **[sponsor](https://github.com/sponsors/supabase)**
|
||||
@@ -176,5 +176,5 @@ Now we're going even bigger with the third installment: `Launch Week III: The Tr
|
||||
- Start using Supabase today: **[app.supabase.io](https://app.supabase.io/)**
|
||||
- Make sure to **[star us on GitHub](https://github.com/supabase/supabase)**
|
||||
- Follow us **[on Twitter](https://twitter.com/supabase)**
|
||||
- Subscribe to our **[YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ)**
|
||||
- Subscribe to our **[YouTube channel](https://www.youtube.com/c/supabase)**
|
||||
- Become a **[sponsor](https://github.com/sponsors/supabase)**
|
||||
@@ -115,5 +115,5 @@ We hit 25K stars!! [github.com/supabase/supabase](http://github.com/supabase/sup
|
||||
- Start using Supabase today: **[app.supabase.io](https://app.supabase.io/)**
|
||||
- Make sure to **[star us on GitHub](https://github.com/supabase/supabase)**
|
||||
- Follow us **[on Twitter](https://twitter.com/supabase)**
|
||||
- Subscribe to our **[YouTube channel](https://www.youtube.com/channel/UCNTVzV1InxHV-YR0fSajqPQ)**
|
||||
- Subscribe to our **[YouTube channel](https://www.youtube.com/c/supabase)**
|
||||
- Become a **[sponsor](https://github.com/sponsors/supabase)**
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: 'Golden Kitty Awards Ceremony Watch Party with Supabase'
|
||||
description: Hang out with us while watching the Product Hunt Golden Kitty Awards Ceremony
|
||||
author: thor_schaeff
|
||||
author_url: https://github.com/thorwebdev
|
||||
author_image_url: https://github.com/thorwebdev.png
|
||||
authorURL: https://github.com/thorwebdev
|
||||
image: product-hunt-golden-kitty-awards-2021.png
|
||||
thumb: product-hunt-golden-kitty-awards-2021-thumb.png
|
||||
tags:
|
||||
- community
|
||||
date: '2022-01-20'
|
||||
toc_depth: 2
|
||||
---
|
||||
|
||||
Last week [Supabase UI](https://ui.supabase.com/), our open-source UI component library inspired by Tailwind and AntDesign, was nominated for the Product Hunt Golden Kitty Awards.
|
||||
|
||||
<blockquote class="twitter-tweet" data-lang="en" data-theme="dark">
|
||||
<p lang="en" dir="ltr">
|
||||
🏆 Supabase UI has been nominated for the{' '}
|
||||
<a href="https://twitter.com/ProductHunt?ref_src=twsrc%5Etfw">@ProductHunt</a>{' '}
|
||||
<a href="https://twitter.com/hashtag/GoldenKittyAwards2021?src=hash&ref_src=twsrc%5Etfw">
|
||||
#GoldenKittyAwards2021
|
||||
</a>{' '}
|
||||
<p>💚 Thank you to all the contributors and upvoters out there, we appreciate you!</p>
|
||||
<a href="https://t.co/xOek6sM4LG">https://t.co/xOek6sM4LG</a>
|
||||
</p>
|
||||
— Supabase (@supabase) <a href="https://twitter.com/supabase/status/1481672574023221260?ref_src=twsrc%5Etfw">January 13, 2022</a>
|
||||
</blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
|
||||
|
||||
Thanks to your contributions and upvotes, Supabase UI has made it to the finals, and to celebrate we'd like to invite you all to hang out with us on Discord (at least whoever manages to stay up) to watch the Awards Ceremony on Thursday Jan 27th at 9am PT (Thursday 5pm GMT, Friday 1am SG).
|
||||
|
||||
## How to join
|
||||
|
||||
[](https://www.producthunt.com/posts/supabase-ui)
|
||||
|
||||
1. [Upvote Supabase UI](https://www.producthunt.com/posts/supabase-ui) (if you haven't already).
|
||||
2. Register for the [Golden Kitty Awards Ceremony 2021](https://app.experiencewelcome.com/events/rPurvb/stages/epflG6).
|
||||
3. Join us [on Discord](https://discord.supabase.com/) in the "hangout" channel.
|
||||
4. Have fun and win Product Hunt & SupaSWAG \o/
|
||||
|
||||
Join our Discord: [discord.supabase.com](https://discord.supabase.com/)
|
||||
|
||||

|
||||
@@ -0,0 +1,156 @@
|
||||
---
|
||||
title: Supabase Beta December 2021
|
||||
description: New crypto extension, Postgres videos, and a bunch of cool integrations.
|
||||
author: paul_copplestone
|
||||
author_url: https://github.com/kiwicopple
|
||||
author_image_url: https://github.com/kiwicopple.png
|
||||
authorURL: https://github.com/kiwicopple
|
||||
image: 2021-dec/release-dec-2021.png
|
||||
thumb: 2021-dec/release-dec-2021-cover.png
|
||||
tags:
|
||||
- release-notes
|
||||
date: '2022-01-20'
|
||||
toc_depth: 3
|
||||
---
|
||||
|
||||
Happy 2022. We're looking forward to a year of shipping product and helping our users build great things!
|
||||
|
||||
Here's a bunch of stuff we shipped throughout December...
|
||||
|
||||
## Hackathon Winners
|
||||
|
||||
We had a ton of awesome projects [submitted for our Holiday Hackdays](/blog/2021/12/17/holiday-hackdays-winners-2021), all of the submissions are open source so come and check out the code for some of our top picks!
|
||||
|
||||
[](/blog/2021/12/17/holiday-hackdays-winners-2021)
|
||||
|
||||
## New crypto extension - pg_crypto
|
||||
|
||||
We just added pg_sodium as an extension inside all postgres instances.
|
||||
Which means you can easily do encryption, decryption, hashing, and cryptographic signing from within your postgres functions and queries.
|
||||
Enable it from the Database > Extensions tab in the [Supabase Dashboard](https://app.supabase.io).
|
||||
|
||||
[]((https://app.supabase.io))
|
||||
|
||||
## PostgreSQL Videos
|
||||
|
||||
### Call Postgres functions from JavaScript with RPC
|
||||
|
||||
Jon Meyers walks you through how to write more complex queries and logic in your database, and invoke it via the API.
|
||||
|
||||
<iframe
|
||||
className="w-full video-with-border"
|
||||
width="640"
|
||||
height="385"
|
||||
src="https://www.youtube-nocookie.com/embed/I6nnp9AINJk"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
|
||||
### Call any API using PostgreSQL functions
|
||||
|
||||
We recently added the ability to call any external HTTP endpoint from your postgres functions. Jon shows you how!
|
||||
|
||||
<iframe
|
||||
className="w-full video-with-border"
|
||||
width="640"
|
||||
height="385"
|
||||
src="https://www.youtube-nocookie.com/embed/rARgrELRCwY"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
|
||||
### Create PostgreSQL Functions with Supabase
|
||||
|
||||
Triggers allow you to execute SQL in response to any table changes. Learn how to write PostgreSQL triggers with Jon!
|
||||
|
||||
<iframe
|
||||
className="w-full video-with-border"
|
||||
width="640"
|
||||
height="385"
|
||||
src="https://www.youtube-nocookie.com/embed/MJZCCpCYEqk"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
|
||||
### Hiring
|
||||
|
||||
We're Hiring - for a ton of new roles across engineering, marketing, and HR. We hire fully remotely, and you'll get to work alongside some incredible people, on one of the fastest growing open source companies. [See which roles we're hiring for](https://about.supabase.com/careers).
|
||||
|
||||
[]((https://about.supabase.com/careers))
|
||||
|
||||
## Community
|
||||
|
||||
There was a lot of activity this month.
|
||||
|
||||
### Learn With Jason
|
||||
|
||||
Let Jason and Jon show you how to build an app with Next.js and Supabase on top of Netlify!
|
||||
|
||||
<iframe
|
||||
className="w-full video-with-border"
|
||||
width="640"
|
||||
height="385"
|
||||
src="https://www.youtube-nocookie.com/embed/8vqY1KT4TLU"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
|
||||
### Divjoy integration
|
||||
|
||||
Divjoy announced their [Supabase Integration](https://divjoy.com/?database=supabase) - The React codebase generator allows you to choose your stack, choose your template, and generate your codebase.
|
||||
|
||||

|
||||
|
||||
### n8n + Supabase
|
||||
|
||||
[n8n](https://n8n.io/) added a Supabase node.
|
||||
|
||||
<blockquote class="twitter-tweet" data-theme="dark"><p lang="en" dir="ltr">We released n8n@0.158.0 with new nodes for <a href="https://twitter.com/supabase?ref_src=twsrc%5Etfw">@supabase</a>, <a href="https://twitter.com/syncromsp?ref_src=twsrc%5Etfw">@syncromsp</a>, and <a href="https://twitter.com/Microsoft?ref_src=twsrc%5Etfw">@Microsoft</a> Graph Security. We also made improvements to existing nodes ✨ <a href="https://t.co/IdeUe4eWBK">pic.twitter.com/IdeUe4eWBK</a></p>— n8n.io (@n8n_io) <a href="https://twitter.com/n8n_io/status/1480502781320572931?ref_src=twsrc%5Etfw">January 10, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
|
||||
|
||||
### Python Upgrades
|
||||
|
||||
The Community released a whole bunch of updated Python libs including: supabase v0.0.4 - gotrue v0.3.0 - realtime v0.0.4 - storage3 v0.1.0
|
||||
|
||||
See the full list of [community supported libs](https://github.com/supabase-community/).
|
||||
|
||||
[]((https://github.com/supabase-community/))
|
||||
|
||||
### Twitter
|
||||
|
||||
We're having a 24/7*365 meme-fest on [Twitter](https://twitter.com/supabase)
|
||||
|
||||
<blockquote class="twitter-tweet" data-theme="dark"><p lang="en" dir="ltr">invest in good relationships <a href="https://t.co/bGiBE7FRFQ">pic.twitter.com/bGiBE7FRFQ</a></p>— Supabase (@supabase) <a href="https://twitter.com/supabase/status/1475876907212316678?ref_src=twsrc%5Etfw">December 28, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
|
||||
|
||||
### TikTok
|
||||
|
||||
Check us out on [TikTok](https://www.tiktok.com/@supabase.com?fromUrl=%2Fsupabase.com&lang=en)
|
||||
|
||||
[]((https://www.tiktok.com/@supabase.com?fromUrl=%2Fsupabase.com&lang=en))
|
||||
|
||||
### Swag Store
|
||||
|
||||
A reminder that we have a [Supabase Swag Store](https://store.supabase.com)
|
||||
|
||||
[]((https://store.supabase.com))
|
||||
|
||||
### GitHub
|
||||
|
||||
We hit 26K stars!!: [github.com/supabase/supabase](http://github.com/supabase/supabase)
|
||||
|
||||

|
||||
|
||||
Source: [repository.surf/supabase](https://repository.surf/supabase)
|
||||
|
||||
Check out some of our other community stats in our latest [Series A Blog Post](/blog/2021/10/28/supabase-series-a).
|
||||
|
||||
## Get started
|
||||
|
||||
- Start using Supabase today: **[app.supabase.io](https://app.supabase.io/)**
|
||||
- Make sure to **[star us on GitHub](https://github.com/supabase/supabase)**
|
||||
- Follow us **[on Twitter](https://twitter.com/supabase)**
|
||||
- Subscribe to our **[YouTube channel](https://www.youtube.com/c/supabase)**
|
||||
- Become a **[sponsor](https://github.com/sponsors/supabase)**
|
||||
@@ -46,6 +46,10 @@
|
||||
{
|
||||
"text": "Terms of service",
|
||||
"url": "/docs/company/terms"
|
||||
},
|
||||
{
|
||||
"text": "Privacy Policy",
|
||||
"url": "/docs/company/privacy"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -615,6 +615,21 @@ module.exports = withMDX({
|
||||
source: '/docs/guides/database/connecting/direct-connections',
|
||||
destination: '/docs/guides/database/connection-pooling',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/guides',
|
||||
destination: '/docs/',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/guides/database/introduction',
|
||||
destination: '/guides/database',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/guides/auth',
|
||||
destination: '/guides/auth/intro',
|
||||
},
|
||||
]
|
||||
},
|
||||
})
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 693 KiB |
Loaded 100 of 114 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user