Merge pull request #5146 from supabase/master

Prod deploy to studio
This commit is contained in:
Isaiah Hamilton authored and GitHub committed 2022-01-24 20:47:49 -05:00
commit 68b7bbe1e7
114 files changed
+3048 -3586

No files matched your search

+10 -3
View File
@@ -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.
+2
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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.
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
View File
@@ -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"]
+16
View File
@@ -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
@@ -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>
+7 -4
View File
@@ -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()
})
+17 -6
View File
@@ -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
View File
@@ -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>
-31
View File
@@ -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
View File
@@ -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
View File
@@ -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.
![OAuth Logins.](/img/supabase-oauth-logins.png)
## 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)
+1 -1
View File
@@ -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.
![Apple Developer Portal.](/img/guides/apple-developer-portal.png)
![Apple Developer Portal.](/img/guides/auth-apple/apple-developer-portal.png)
### Obtain an App ID
+5 -5
View File
@@ -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.
![Register an application.](/img/guides/azure-register-app.png)
![Register an application.](/img/guides/auth-azure/azure-register-app.png)
### 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 the client id](/img/guides/azure-client-id.png)
![Obtain the client id](/img/guides/auth-azure/azure-client-id.png)
### 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).
![Obtain the client secret](/img/guides/azure-client-secret.png)
![Obtain the client secret](/img/guides/auth-azure/azure-client-secret.png)
### 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',
})
+1 -1
View File
@@ -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.
![Bitbucket Developer Portal.](/img/guides/bitbucket-portal.png)
![Bitbucket Developer Portal.](/img/guides/auth-bitbucket/bitbucket-portal.png)
### Find your callback URL
+2 -2
View File
@@ -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.
![Discord Portal.](/img/guides/discord-portal.png)
![Discord Portal.](/img/guides/auth-discord/discord-portal.png)
- Once logged in, go to [discord.com/developers](https://discord.com/developers).
![Discord Portal.](/img/guides/discord-developer-portal.png)
![Discord Portal.](/img/guides/auth-discord/discord-developer-portal.png)
### Find your callback URL
+1 -1
View File
@@ -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.
![Facebook Developer Portal.](/img/guides/facebook-portal.png)
![Facebook Developer Portal.](/img/guides/auth-facebook/facebook-portal.png)
### Create a Facebook App
+1 -1
View File
@@ -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.
![GitHub Developer Portal.](/img/guides/github-portal.png)
![GitHub Developer Portal.](/img/guides/auth-github/github-portal.png)
### Create a GitHub Oauth App
+1 -1
View File
@@ -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.
![GitLab Developer Portal.](/img/guides/gitlab-portal.png)
![GitLab Developer Portal.](/img/guides/auth-gitlab/gitlab-portal.png)
### Find your callback URL
+1 -1
View File
@@ -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.
![Google Developer Portal.](/img/guides/google-portal.png)
![Google Developer Portal.](/img/guides/auth-google/google-portal.png)
### Create a Google Cloud Platform Project
+1 -1
View File
@@ -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.
![Slack Developer Portal.](/img/guides/slack-portal.png)
![Slack Developer Portal.](/img/guides/auth-slack/slack-portal.png)
### Find your callback URL
+1 -1
View File
@@ -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)
![Spotify Developer Portal.](/img/guides/spotify-portal.png)
![Spotify Developer Portal.](/img/guides/auth-spotify/spotify-portal.png)
### Find your callback URL
+6 -6
View File
@@ -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.
![Twitch Developer Page](/img/guides/twitch-developer-page.png)
![Twitch Developer Page](/img/guides/auth-twitch/twitch-developer-page.png)
- Once logged in, go to the [Twitch Developer Console](https://dev.twitch.tv/console).
![Twitch Developer Console](/img/guides/twitch-console.png)
![Twitch Developer Console](/img/guides/auth-twitch/twitch-console.png)
### 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
![Twitch Developer Console](/img/guides/twitch-console.png)
![Twitch Developer Console](/img/guides/auth-twitch/twitch-console.png)
- Click on `+ Register Your Application` at the top right.
![Register Application](/img/guides/twitch-register-your-application.png)
![Register Application](/img/guides/auth-twitch/twitch-register-your-application.png)
- 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.
![Twitch Applications List](/img/guides/twitch-applications-list.png)
![Twitch Applications List](/img/guides/auth-twitch/twitch-applications-list.png)
- Copy your Client ID.
- Click `New Secret` to create a new Client Secret.
- Copy your Client Secret.
![Get Client ID and Secret](/img/guides/twitch-get-keys.png)
![Get Client ID and Secret](/img/guides/auth-twitch/twitch-get-keys.png)
### Add your Twitch credentials into your Supabase Project
+1 -1
View File
@@ -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.
![Twitter Developer Portal.](/img/guides/twitter-portal.png)
![Twitter Developer Portal.](/img/guides/auth-twitter/twitter-portal.png)
### Find your callback URL
+15 -2
View File
@@ -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
+1 -1
View File
@@ -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>
-37
View File
@@ -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.
-->
+52 -3
View File
@@ -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)
+2 -2
View File
@@ -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
+14 -6
View File
@@ -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.
-7
View File
@@ -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).
+519
View File
@@ -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)
+9 -2
View File
@@ -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.
+1 -8
View File
@@ -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',
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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();
+1
View File
@@ -21,6 +21,7 @@ Jon M
Jonny Summers-Muir
Joshen Lim
Kang Ming Tay
Lakshmipathi G
Laura C
Leo T
Mark Burggraf
@@ -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
![This image shows GitHub star growth.](/images/blog/blog/dec-starcount.png)
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)**
+1 -1
View File
@@ -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&amp;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>
&mdash; 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
[![Product hunt](https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=290768&theme=light)](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/)
![Discord Hangout](/images/blog/hackathon/community.png)
@@ -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!
[![hackathon winners](/images/blog/2021-dec/hackathon.png)](/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).
[![pg sodium support](/images/blog/2021-dec/pgsodium.png)]((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).
[![supabase is hiring](/images/blog/2021-dec/hiring.png)]((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.
![pg sodium support](/images/blog/2021-dec/divjoy.png)
### 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>&mdash; 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/).
[![supabase community libs](/images/blog/2021-dec/python.png)]((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>&mdash; 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)
[![tiktok](/images/blog/2021-dec/tiktok.png)]((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)
[![swag store](/images/blog/2021-dec/swag.jpg)]((https://store.supabase.com))
### GitHub
We hit 26K stars!!: [github.com/supabase/supabase](http://github.com/supabase/supabase)
![GitHub](/images/blog/2021-dec/stars.png)
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)**
+4
View File
@@ -46,6 +46,10 @@
{
"text": "Terms of service",
"url": "/docs/company/terms"
},
{
"text": "Privacy Policy",
"url": "/docs/company/privacy"
}
]
},
+15
View File
@@ -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