mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 15:19:08 +03:00
Merge branch 'master' into feat/logflare-workflow-run-logs
This commit is contained in:
commit
32cb7f2e3d
35 files changed
+1807
-448
No files matched your search
@@ -1,10 +1,12 @@
|
||||
import { type PropsWithChildren } from 'react'
|
||||
|
||||
import { IS_PLATFORM } from 'common'
|
||||
|
||||
import { supabaseMisc } from '~/lib/supabaseMisc'
|
||||
import Layout from '~/layouts/guides'
|
||||
|
||||
const GuidesLayout = async ({ children }: PropsWithChildren) => {
|
||||
const partners = await getPartners()
|
||||
const partners = IS_PLATFORM ? await getPartners() : []
|
||||
const partnerNavItems = partners.map((partner) => ({
|
||||
name: partner.title,
|
||||
url: `https://supabase.com/partners/integrations/${partner.slug}` as `https://${string}`,
|
||||
|
||||
@@ -34,7 +34,7 @@ pg_cron is not fully supported on Fly Postgres. Read more about this [Fly Postgr
|
||||
|
||||
```sql
|
||||
-- Example: enable the "pg_cron" extension
|
||||
create extension pg_cron with schema extensions;
|
||||
create extension pg_cron with schema pg_catalog;
|
||||
|
||||
grant usage on schema cron to postgres;
|
||||
grant all privileges on all tables in schema cron to postgres;
|
||||
|
||||
@@ -148,9 +148,8 @@ Do not log Personal Identifiable Information (PII) within the `User-Agent` heade
|
||||
To enable query logs for other categories of statements:
|
||||
|
||||
1. [Enable the pgAudit extension](https://supabase.com/dashboard/project/_/database/extensions).
|
||||
2. [Restart your project](https://supabase.com/dashboard/project/_/settings/general) using the **Fast database reboot** option.
|
||||
3. Configure `pgaudit.log` (see below). Perform a fast reboot if needed.
|
||||
4. View your query logs under [Logs > Postgres Logs](https://supabase.com/dashboard/project/_/logs/postgres-logs).
|
||||
2. Configure `pgaudit.log` (see below). Perform a fast reboot if needed.
|
||||
3. View your query logs under [Logs > Postgres Logs](https://supabase.com/dashboard/project/_/logs/postgres-logs).
|
||||
|
||||
### Configuring `pgaudit.log`
|
||||
|
||||
|
||||
@@ -31,14 +31,18 @@ To get started:
|
||||
You can use the `get` subcommand of the CLI to check whether SSL is currently being enforced:
|
||||
|
||||
```bash
|
||||
> supabase ssl-enforcement --project-ref {ref} get --experimental
|
||||
supabase ssl-enforcement --project-ref {ref} get --experimental
|
||||
```
|
||||
|
||||
Response if SSL is being enforced:
|
||||
|
||||
```bash
|
||||
SSL is being enforced.
|
||||
```
|
||||
|
||||
Or similarly, if SSL is not being enforced, you will see:
|
||||
Response if SSL is not being enforced:
|
||||
|
||||
```bash
|
||||
> supabase ssl-enforcement --project-ref {ref} get --experimental
|
||||
SSL is *NOT* being enforced.
|
||||
```
|
||||
|
||||
@@ -47,15 +51,13 @@ SSL is *NOT* being enforced.
|
||||
The `update` subcommand is used to change the SSL enforcement status for your project:
|
||||
|
||||
```bash
|
||||
> supabase ssl-enforcement --project-ref {ref} update --enable-db-ssl-enforcement --experimental
|
||||
SSL is now being enforced.
|
||||
supabase ssl-enforcement --project-ref {ref} update --enable-db-ssl-enforcement --experimental
|
||||
```
|
||||
|
||||
Similarly, to disable SSL enforcement:
|
||||
|
||||
```bash
|
||||
> supabase ssl-enforcement --project-ref {ref} update --disable-db-ssl-enforcement --experimental
|
||||
SSL is *NOT* being enforced.
|
||||
supabase ssl-enforcement --project-ref {ref} update --disable-db-ssl-enforcement --experimental
|
||||
```
|
||||
|
||||
### A note about Postgres SSL modes
|
||||
@@ -67,11 +69,11 @@ The strongest mode offered by Postgres is `verify-full` and this is the mode you
|
||||
Once the CA certificate has been downloaded, add it to the certificate authority list used by Postgres.
|
||||
|
||||
```bash
|
||||
> cat {location of downloaded prod-ca-2021.crt} >> ~/.postgres/root.crt
|
||||
cat {location of downloaded prod-ca-2021.crt} >> ~/.postgres/root.crt
|
||||
```
|
||||
|
||||
With the CA certificate added to the trusted certificate authorities list, use `psql` or your client library to connect to Supabase:
|
||||
|
||||
```bash
|
||||
> psql "postgresql://aws-0-eu-central-1.pooler.supabase.com:6543/postgres?sslmode=verify-full" -U postgres.<user>
|
||||
psql "postgresql://aws-0-eu-central-1.pooler.supabase.com:6543/postgres?sslmode=verify-full" -U postgres.<user>
|
||||
```
|
||||
@@ -107,14 +107,25 @@ You can add new Functions as `volumes/functions/<FUNCTION_NAME>/index.ts`. Resta
|
||||
|
||||
### Accessing Postgres
|
||||
|
||||
You can connect to the Postgres database locally on port `5432`. For example, if you have `psql` on your local machine you can run:
|
||||
By default, the Supabase stack runs the [Supavisor](https://supabase.github.io/supavisor/development/docs/) connection pooler. Supavisor provides efficient management of database connections.
|
||||
|
||||
You can connect to the Postgres database using the following methods:
|
||||
|
||||
1. For session-based connections (equivalent to direct PostgreSQL connections):
|
||||
|
||||
```bash
|
||||
psql -h 127.0.0.1 -p 5432 -d postgres -U postgres
|
||||
psql 'postgres://postgres.your-tenant-id:your-super-secret-and-long-postgres-password@localhost:5432/postgres'
|
||||
```
|
||||
|
||||
The default password is `your-super-secret-and-long-postgres-password`. You should change this as soon as possible using the [instructions below](#update-secrets). By default the database is not accessible from outside the local machine. You can [change this](#exposing-your-postgres-database) by updating the `docker-compose.yml` file.
|
||||
2. For pooled transactional connections:
|
||||
|
||||
```bash
|
||||
psql 'postgres://postgres.your-tenant-id:your-super-secret-and-long-postgres-password@localhost:6543/postgres'
|
||||
```
|
||||
|
||||
The default tenant ID is `your-tenant-id`, and the default password is `your-super-secret-and-long-postgres-password`. You should change these as soon as possible using the [instructions below](#update-secrets).
|
||||
|
||||
By default, the database is not accessible from outside the local machine but the pooler is. You can [change this](#exposing-your-postgres-database) by updating the `docker-compose.yml` file.
|
||||
|
||||
## Securing your services
|
||||
|
||||
@@ -145,6 +156,7 @@ Update the `.env` file with your own secrets. In particular, these are required:
|
||||
- `JWT_SECRET`: used by PostgREST and GoTrue, among others.
|
||||
- `SITE_URL`: the base URL of your site.
|
||||
- `SMTP_*`: mail server credentials. You can use any SMTP server.
|
||||
- `POOLER_TENANT_ID`: the tenant-id that will be used by supavisor pooler for your connection string
|
||||
|
||||
You will need to [restart](#restarting-all-services) the services for the changes to take effect.
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ Andrew Valleteau
|
||||
Angelico de los Reyes
|
||||
Ant Wilson
|
||||
Ariuna K
|
||||
Artur Zakirov
|
||||
Beng Eu
|
||||
Bo Lu
|
||||
Bobbie Soedirgo
|
||||
@@ -100,7 +101,7 @@ TECHNOLOGIES
|
||||
|
||||
Supabase would not be possible without the work of some other amazing open source contributors.
|
||||
|
||||
- Postgres - https://www.postgresql.org/docs/devel/git.html
|
||||
- Postgres - https://www.postgresql.org/
|
||||
- PostgREST - https://github.com/PostgREST
|
||||
- Kong - https://github.com/kong/
|
||||
- Next.js - https://nextjs.org/
|
||||
|
||||
@@ -1497,7 +1497,7 @@ functions:
|
||||
code: |
|
||||
```c#
|
||||
var result = await supabase.From<City>()
|
||||
.Where(x => x.Name == null
|
||||
.Where(x => x.Name == null)
|
||||
.Get();
|
||||
```
|
||||
|
||||
|
||||
+5
-4
@@ -21,8 +21,8 @@ const ContentFile = ({ projectKeys }: ContentFileProps) => {
|
||||
<ConnectTabContent value=".env.local">
|
||||
<SimpleCodeBlock className="bash" parentClassName="min-h-72">
|
||||
{`
|
||||
SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'}
|
||||
SUPABASE_ANON_KEY=${projectKeys.anonKey ?? 'your-anon-key'}
|
||||
PUBLIC_SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'}
|
||||
PUBLIC_SUPABASE_ANON_KEY=${projectKeys.anonKey ?? 'your-anon-key'}
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
@@ -31,9 +31,10 @@ SUPABASE_ANON_KEY=${projectKeys.anonKey ?? 'your-anon-key'}
|
||||
<SimpleCodeBlock className="js" parentClassName="min-h-72">
|
||||
{`
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from "$env/static/public"
|
||||
|
||||
const supabaseUrl = process.env.SUPABASE_URL;
|
||||
const supabaseKey = process.env.SUPABASE_ANON_KEY;
|
||||
const supabaseUrl = PUBLIC_SUPABASE_URL;
|
||||
const supabaseKey = PUBLIC_SUPABASE_ANON_KEY;
|
||||
|
||||
export const supabase = createClient(supabaseUrl, supabaseKey);
|
||||
`}
|
||||
|
||||
@@ -200,14 +200,14 @@ const CreateBucketModal = ({ visible, onClose }: CreateBucketModalProps) => {
|
||||
{IS_PLATFORM && (
|
||||
<div className="col-span-12">
|
||||
<p className="text-foreground-light text-sm">
|
||||
Note: The{' '}
|
||||
Note: Individual bucket uploads will still be capped at the{' '}
|
||||
<Link
|
||||
href={`/project/${ref}/settings/storage`}
|
||||
className="text-brand opacity-80 hover:opacity-100 transition"
|
||||
className="font-bold underline"
|
||||
>
|
||||
global upload limit
|
||||
</Link>{' '}
|
||||
takes precedence over this value ({formattedGlobalUploadLimit})
|
||||
of {formattedGlobalUploadLimit}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -204,16 +204,16 @@ const EditBucketModal = ({ visible, bucket, onClose }: EditBucketModalProps) =>
|
||||
</Listbox>
|
||||
</div>
|
||||
{IS_PLATFORM && (
|
||||
<div className="col-span-12">
|
||||
<div className="col-span-12 mt-2">
|
||||
<p className="text-foreground-light text-sm">
|
||||
Note: The{' '}
|
||||
Note: Individual bucket upload will still be capped at the{' '}
|
||||
<Link
|
||||
href={`/project/${ref}/settings/storage`}
|
||||
className="text-brand opacity-80 hover:opacity-100 transition"
|
||||
className="font-bold underline"
|
||||
>
|
||||
global upload limit
|
||||
</Link>{' '}
|
||||
takes precedence over this value ({formattedGlobalUploadLimit})
|
||||
of {formattedGlobalUploadLimit}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -2,6 +2,8 @@ import { compact, uniqBy } from 'lodash'
|
||||
import { Item, Menu, Separator, Submenu } from 'react-contexify'
|
||||
import 'react-contexify/dist/ReactContexify.css'
|
||||
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore'
|
||||
import { ChevronRight, ChevronsDown, ChevronsUp, Clipboard, Eye, FolderPlus } from 'lucide-react'
|
||||
import {
|
||||
@@ -16,6 +18,7 @@ interface ColumnContextMenuProps {
|
||||
}
|
||||
|
||||
const ColumnContextMenu = ({ id = '' }: ColumnContextMenuProps) => {
|
||||
const canUpdateFiles = useCheckPermissions(PermissionAction.STORAGE_WRITE, '*')
|
||||
const storageExplorerStore = useStorageStore()
|
||||
const {
|
||||
columns,
|
||||
@@ -57,11 +60,15 @@ const ColumnContextMenu = ({ id = '' }: ColumnContextMenuProps) => {
|
||||
|
||||
return (
|
||||
<Menu id={id} animation="fade">
|
||||
<Item onClick={({ props }) => onSelectCreateFolder(props.index)}>
|
||||
<FolderPlus size="14" strokeWidth={1} />
|
||||
<span className="ml-2 text-xs">New folder</span>
|
||||
</Item>
|
||||
<Separator />
|
||||
{canUpdateFiles && (
|
||||
<>
|
||||
<Item onClick={({ props }) => onSelectCreateFolder(props.index)}>
|
||||
<FolderPlus size="14" strokeWidth={1} />
|
||||
<span className="ml-2 text-xs">New folder</span>
|
||||
</Item>
|
||||
<Separator />
|
||||
</>
|
||||
)}
|
||||
<Item onClick={({ props }) => onSelectAllItemsInColumn(props.index)}>
|
||||
<Clipboard size="14" strokeWidth={1} />
|
||||
<span className="ml-2 text-xs">Select all items</span>
|
||||
|
||||
@@ -183,7 +183,7 @@ const FileExplorerHeader = ({
|
||||
|
||||
const breadcrumbs = columns.map((column) => column.name)
|
||||
const backDisabled = columns.length <= 1
|
||||
const canUpdateStorage = useCheckPermissions(PermissionAction.STORAGE_ADMIN_WRITE, '*')
|
||||
const canUpdateStorage = useCheckPermissions(PermissionAction.STORAGE_WRITE, '*')
|
||||
|
||||
useEffect(() => {
|
||||
if (itemSearchString) setSearchString(itemSearchString)
|
||||
|
||||
+24
-5
@@ -1,8 +1,13 @@
|
||||
import { Download, Move, Trash2, X } from 'lucide-react'
|
||||
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
|
||||
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
|
||||
import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore'
|
||||
import { X, Download, Trash2, Move } from 'lucide-react'
|
||||
import { Button } from 'ui'
|
||||
|
||||
const FileExplorerHeaderSelection = () => {
|
||||
const canUpdateFiles = useCheckPermissions(PermissionAction.STORAGE_WRITE, '*')
|
||||
const storageExplorerStore = useStorageStore()
|
||||
const {
|
||||
selectedItems,
|
||||
@@ -39,20 +44,34 @@ const FileExplorerHeaderSelection = () => {
|
||||
Download
|
||||
</Button>
|
||||
<div className="border-r border-green-900 py-3 opacity-50" />
|
||||
<Button
|
||||
|
||||
<ButtonTooltip
|
||||
icon={<Trash2 size={16} strokeWidth={2} />}
|
||||
type="primary"
|
||||
onClick={() => setSelectedItemsToDelete(selectedItems)}
|
||||
tooltip={{
|
||||
content: {
|
||||
side: 'bottom',
|
||||
text: !canUpdateFiles ? 'You need additional permissions to delete files' : undefined,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button
|
||||
</ButtonTooltip>
|
||||
|
||||
<ButtonTooltip
|
||||
icon={<Move size={16} strokeWidth={2} />}
|
||||
type="primary"
|
||||
onClick={() => setSelectedItemsToMove(selectedItems)}
|
||||
tooltip={{
|
||||
content: {
|
||||
side: 'bottom',
|
||||
text: !canUpdateFiles ? 'You need additional permissions to move files' : undefined,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Move
|
||||
</Button>
|
||||
</ButtonTooltip>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -139,7 +139,7 @@ const FileExplorerRow: ItemRenderer<StorageItem, FileExplorerRowProps> = ({
|
||||
const isOpened =
|
||||
openedFolders.length > columnIndex ? isEqual(openedFolders[columnIndex], item) : false
|
||||
const isPreviewed = !isEmpty(selectedFilePreview) && isEqual(selectedFilePreview?.id, item.id)
|
||||
const canUpdateFiles = useCheckPermissions(PermissionAction.STORAGE_ADMIN_WRITE, '*')
|
||||
const canUpdateFiles = useCheckPermissions(PermissionAction.STORAGE_WRITE, '*')
|
||||
|
||||
const { show } = useContextMenu()
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const FolderContextMenu = ({ id = '' }: FolderContextMenuProps) => {
|
||||
const storageExplorerStore = useStorageStore()
|
||||
const { openedFolders, downloadFolder, setSelectedItemToRename, setSelectedItemsToDelete } =
|
||||
storageExplorerStore
|
||||
const canUpdateFiles = useCheckPermissions(PermissionAction.STORAGE_ADMIN_WRITE, '*')
|
||||
const canUpdateFiles = useCheckPermissions(PermissionAction.STORAGE_WRITE, '*')
|
||||
|
||||
return (
|
||||
<Menu id={id} animation="fade">
|
||||
|
||||
@@ -27,7 +27,7 @@ const ItemContextMenu = ({ id = '' }: ItemContextMenuProps) => {
|
||||
} = storageExplorerStore
|
||||
const { onCopyUrl } = useCopyUrl(storageExplorerStore.projectRef)
|
||||
const isPublic = selectedBucket.public
|
||||
const canUpdateFiles = useCheckPermissions(PermissionAction.STORAGE_ADMIN_WRITE, '*')
|
||||
const canUpdateFiles = useCheckPermissions(PermissionAction.STORAGE_WRITE, '*')
|
||||
|
||||
const onHandleClick = async (event: any, item: StorageItemWithColumn, expiresIn?: number) => {
|
||||
if (item.isCorrupted) return
|
||||
|
||||
@@ -107,7 +107,7 @@ const PreviewPane = () => {
|
||||
} = storageExplorerStore
|
||||
const { onCopyUrl } = useCopyUrl(storageExplorerStore.projectRef)
|
||||
|
||||
const canUpdateFiles = useCheckPermissions(PermissionAction.STORAGE_ADMIN_WRITE, '*')
|
||||
const canUpdateFiles = useCheckPermissions(PermissionAction.STORAGE_WRITE, '*')
|
||||
|
||||
if (!file) {
|
||||
return null
|
||||
|
||||
@@ -33,7 +33,7 @@ export const S3Connection = () => {
|
||||
const [openDeleteDialog, setOpenDeleteDialog] = useState(false)
|
||||
const [deleteCred, setDeleteCred] = useState<{ id: string; description: string }>()
|
||||
|
||||
const canReadS3Credentials = useCheckPermissions(PermissionAction.STORAGE_ADMIN_WRITE, '*')
|
||||
const canReadS3Credentials = useCheckPermissions(PermissionAction.STORAGE_ADMIN_READ, '*')
|
||||
|
||||
const { data: projectAPI } = useProjectApiQuery({ projectRef: projectRef })
|
||||
const { data: storageCreds, ...storageCredsQuery } = useStorageCredentialsQuery(
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import * as Sentry from '@sentry/nextjs'
|
||||
|
||||
export async function register() {
|
||||
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
||||
await import('./sentry.server.config')
|
||||
}
|
||||
|
||||
if (process.env.NEXT_RUNTIME === 'edge') {
|
||||
await import('./sentry.edge.config')
|
||||
}
|
||||
}
|
||||
|
||||
export const onRequestError = Sentry.captureRequestError
|
||||
@@ -1,4 +1,3 @@
|
||||
import { withSentry } from '@sentry/nextjs'
|
||||
import { isResponseOk } from 'lib/common/fetch'
|
||||
import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
|
||||
import { IS_PLATFORM } from '../constants'
|
||||
@@ -30,9 +29,7 @@ export default async function apiWrapper(
|
||||
}
|
||||
}
|
||||
|
||||
const func = withSentry(handler as any)
|
||||
// @ts-ignore
|
||||
return await func(req, res)
|
||||
return handler(req, res)
|
||||
} catch (error) {
|
||||
return res.status(500).json({ error })
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import { IS_PLATFORM } from 'lib/constants'
|
||||
import { lookupMime } from 'lib/mime'
|
||||
import { PROJECT_ENDPOINT_PROTOCOL } from 'pages/api/constants'
|
||||
import { Button, SONNER_DEFAULT_DURATION, SonnerProgress } from 'ui'
|
||||
import Link from 'next/link'
|
||||
|
||||
type CachedFile = { id: string; fetchedAt: number; expiresIn: number; url: string }
|
||||
|
||||
@@ -265,7 +266,6 @@ class StorageExplorerStore {
|
||||
popOpenedFoldersAtIndex = (index: number) => {
|
||||
this.openedFolders = this.openedFolders.slice(0, index + 1)
|
||||
}
|
||||
|
||||
clearOpenedFolders = () => {
|
||||
this.openedFolders = []
|
||||
}
|
||||
@@ -566,13 +566,22 @@ class StorageExplorerStore {
|
||||
<p className="text-foreground">
|
||||
Failed to upload {numberOfFilesRejected} file{numberOfFilesRejected > 1 ? 's' : ''} as{' '}
|
||||
{numberOfFilesRejected > 1 ? 'their' : 'its'} size
|
||||
{numberOfFilesRejected > 1 ? 's are' : ' is'} beyond the upload limit of {value}
|
||||
{numberOfFilesRejected > 1 ? 's are' : ' is'} beyond the global upload limit of {value}
|
||||
{unit}.
|
||||
</p>
|
||||
<p className="text-foreground-light">
|
||||
You may change the file size upload limit under Storage in Project Settings.
|
||||
You can change the global file size upload limit in{' '}
|
||||
<Link
|
||||
className="underline"
|
||||
href={`/project/${this.projectRef}/settings/storage`}
|
||||
target="_blank"
|
||||
>
|
||||
Storage settings
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</div>,
|
||||
{ duration: 8000 }
|
||||
)
|
||||
|
||||
if (numberOfFilesRejected === filesToUpload.length) return
|
||||
@@ -733,26 +742,31 @@ class StorageExplorerStore {
|
||||
chunkSize,
|
||||
onShouldRetry(error) {
|
||||
const status = error.originalResponse ? error.originalResponse.getStatus() : 0
|
||||
const doNotRetryStatuses = [400, 403, 404, 409, 415, 429]
|
||||
const doNotRetryStatuses = [400, 403, 404, 409, 413, 415, 429]
|
||||
|
||||
return !doNotRetryStatuses.includes(status)
|
||||
},
|
||||
onError: (error) => {
|
||||
numberOfFilesUploadedFail += 1
|
||||
if (
|
||||
error instanceof tus.DetailedError &&
|
||||
error.originalResponse?.getStatus() === 415
|
||||
) {
|
||||
// Unsupported mime type
|
||||
toast.error(
|
||||
capitalize(
|
||||
error.originalResponse.getBody() ||
|
||||
`Failed to upload ${file.name}: ${metadata.mimetype} is not allowed`
|
||||
),
|
||||
{
|
||||
description: `Allowed MIME types: ${this.selectedBucket.allowed_mime_types?.join(', ')}`,
|
||||
}
|
||||
)
|
||||
if (error instanceof tus.DetailedError) {
|
||||
const status = error.originalResponse?.getStatus()
|
||||
if (status === 415) {
|
||||
// Unsupported mime type
|
||||
toast.error(
|
||||
capitalize(
|
||||
error?.originalResponse?.getBody() ||
|
||||
`Failed to upload ${file.name}: ${metadata.mimetype} is not allowed`
|
||||
),
|
||||
{
|
||||
description: `Allowed MIME types: ${this.selectedBucket.allowed_mime_types?.join(', ')}`,
|
||||
}
|
||||
)
|
||||
} else if (status === 413) {
|
||||
// Payload too large
|
||||
toast.error(
|
||||
`Failed to upload ${file.name}: File size exceeds the bucket upload limit.`
|
||||
)
|
||||
}
|
||||
} else {
|
||||
toast.error(`Failed to upload ${file.name}: ${error.message}`)
|
||||
}
|
||||
|
||||
+27
-20
@@ -520,29 +520,36 @@ const nextConfig = {
|
||||
// ensure that your source maps include changes from all other Webpack plugins
|
||||
module.exports =
|
||||
process.env.NEXT_PUBLIC_IS_PLATFORM === 'true'
|
||||
? withSentryConfig(
|
||||
withBundleAnalyzer(nextConfig),
|
||||
{
|
||||
silent: true,
|
||||
? withSentryConfig(withBundleAnalyzer(nextConfig), {
|
||||
silent: true,
|
||||
|
||||
// For all available options, see:
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
|
||||
|
||||
// Upload a larger set of source maps for prettier stack traces (increases build time)
|
||||
widenClientFileUpload: true,
|
||||
|
||||
// Automatically annotate React components to show their full name in breadcrumbs and session replay
|
||||
reactComponentAnnotation: {
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
// For all available options, see:
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
|
||||
|
||||
// Upload a larger set of source maps for prettier stack traces (increases build time)
|
||||
widenClientFileUpload: true,
|
||||
// Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
|
||||
// This can increase your server load as well as your hosting bill.
|
||||
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
|
||||
// side errors will fail.
|
||||
tunnelRoute: '/monitoring',
|
||||
|
||||
// Transpiles SDK to be compatible with IE11 (increases bundle size)
|
||||
transpileClientSDK: false,
|
||||
// Hides source maps from generated client bundles
|
||||
hideSourceMaps: true,
|
||||
|
||||
// Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load)
|
||||
tunnelRoute: '/monitoring',
|
||||
// Automatically tree-shake Sentry logger statements to reduce bundle size
|
||||
disableLogger: true,
|
||||
|
||||
// Hides source maps from generated client bundles
|
||||
hideSourceMaps: true,
|
||||
|
||||
// Automatically tree-shake Sentry logger statements to reduce bundle size
|
||||
disableLogger: true,
|
||||
}
|
||||
)
|
||||
// Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
|
||||
// See the following for more information:
|
||||
// https://docs.sentry.io/product/crons/
|
||||
// https://vercel.com/docs/cron-jobs
|
||||
automaticVercelMonitors: true,
|
||||
})
|
||||
: nextConfig
|
||||
@@ -33,14 +33,14 @@
|
||||
"@next/bundle-analyzer": "^14.2.3",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@sentry/nextjs": "^7.108.0",
|
||||
"@sentry/nextjs": "^8.34.0",
|
||||
"@stripe/react-stripe-js": "^2.5.0",
|
||||
"@stripe/stripe-js": "^3.0.5",
|
||||
"@supabase/auth-helpers-react": "^0.4.2",
|
||||
"@supabase/auth-js": "2.64.2",
|
||||
"@supabase/pg-meta": "*",
|
||||
"@supabase/realtime-js": "2.10.2",
|
||||
"@supabase/shared-types": "0.1.73",
|
||||
"@supabase/shared-types": "0.1.74",
|
||||
"@supabase/supabase-js": "^2.44.3",
|
||||
"@tanstack/react-query": "4.35.7",
|
||||
"@tanstack/react-query-devtools": "4.35.7",
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import * as Sentry from '@sentry/nextjs'
|
||||
import Error from 'next/error'
|
||||
|
||||
const CustomErrorComponent = (props) => {
|
||||
return <Error statusCode={props.statusCode} />
|
||||
}
|
||||
|
||||
CustomErrorComponent.getInitialProps = async (contextData) => {
|
||||
// In case this is running in a serverless function, await this in order to give Sentry
|
||||
// time to send the error before the lambda exits
|
||||
await Sentry.captureUnderscoreErrorException(contextData)
|
||||
|
||||
// This will contain the status code of the response
|
||||
return Error.getInitialProps(contextData)
|
||||
}
|
||||
|
||||
export default CustomErrorComponent
|
||||
@@ -1,3 +1,7 @@
|
||||
// This file configures the initialization of Sentry on the client.
|
||||
// The config you add here will be used whenever a users loads a page in their browser.
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
|
||||
import * as Sentry from '@sentry/nextjs'
|
||||
import { IS_PLATFORM } from 'common/constants/environment'
|
||||
import { LOCAL_STORAGE_KEYS } from 'common/constants/local-storage'
|
||||
@@ -5,8 +9,12 @@ import { match } from 'path-to-regexp'
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
|
||||
tracesSampleRate: 0.01,
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
|
||||
beforeSend(event, hint) {
|
||||
const consent =
|
||||
typeof window !== 'undefined'
|
||||
@@ -25,13 +33,14 @@ Sentry.init({
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
integrations: [
|
||||
new Sentry.BrowserTracing({
|
||||
Sentry.browserTracingIntegration({
|
||||
// TODO: update gotrue + api to support Access-Control-Request-Headers: authorization,baggage,sentry-trace,x-client-info
|
||||
// then remove these options
|
||||
traceFetch: false,
|
||||
traceXHR: false,
|
||||
beforeNavigate: (context) => {
|
||||
beforeStartSpan: (context) => {
|
||||
return {
|
||||
...context,
|
||||
name: standardiseRouterUrl(location.pathname),
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
|
||||
// The config you add here will be used whenever one of the edge features is loaded.
|
||||
// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
|
||||
import * as Sentry from '@sentry/nextjs'
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
|
||||
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
|
||||
tracesSampleRate: 0.01,
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
})
|
||||
@@ -1,9 +1,18 @@
|
||||
// This file configures the initialization of Sentry on the server.
|
||||
// The config you add here will be used whenever the server handles a request.
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
|
||||
import * as Sentry from '@sentry/nextjs'
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
|
||||
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
|
||||
tracesSampleRate: 0.01,
|
||||
|
||||
// Setting this option to true will print useful information to the console while you're setting up Sentry.
|
||||
debug: false,
|
||||
|
||||
ignoreErrors: [
|
||||
// Used exclusively in Monaco Editor.
|
||||
'ResizeObserver',
|
||||
|
||||
@@ -89,21 +89,20 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||
}, [router.isReady, consentValue])
|
||||
|
||||
useEffect(() => {
|
||||
if (blockEvents) return
|
||||
|
||||
const handleBeforeUnload = async () => {
|
||||
await post(`${API_URL}/telemetry/page-leave`, {
|
||||
page_url: window.location.href,
|
||||
page_title: title,
|
||||
pathname: router.pathname,
|
||||
})
|
||||
if (!blockEvents) {
|
||||
await post(`${API_URL}/telemetry/page-leave`, {
|
||||
page_url: window.location.href,
|
||||
page_title: title,
|
||||
pathname: router.pathname,
|
||||
})
|
||||
}
|
||||
}
|
||||
window.addEventListener('beforeunload', handleBeforeUnload)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload)
|
||||
}
|
||||
}, [])
|
||||
}, [blockEvents, router.pathname, title])
|
||||
|
||||
const site_title = `${APP_NAME} | The Open Source Firebase Alternative`
|
||||
const { basePath } = useRouter()
|
||||
|
||||
@@ -25,6 +25,7 @@ POSTGRES_PORT=5432
|
||||
POOLER_PROXY_PORT_TRANSACTION=6543
|
||||
POOLER_DEFAULT_POOL_SIZE=20
|
||||
POOLER_MAX_CLIENT_CONN=100
|
||||
POOLER_TENANT_ID=your-tenant-id
|
||||
|
||||
|
||||
############
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
|
||||
minio:
|
||||
|
||||
@@ -455,8 +455,8 @@ services:
|
||||
- /app/bin/migrate && /app/bin/supavisor eval "$$(cat /etc/pooler/pooler.exs)" && /app/bin/server
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- ${POSTGRES_PORT}:${POSTGRES_PORT}
|
||||
- ${POOLER_PROXY_PORT_TRANSACTION}:${POOLER_PROXY_PORT_TRANSACTION}
|
||||
- ${POSTGRES_PORT}:5432
|
||||
- ${POOLER_PROXY_PORT_TRANSACTION}:6543
|
||||
environment:
|
||||
- PORT=4000
|
||||
- POSTGRES_PORT=${POSTGRES_PORT}
|
||||
@@ -470,7 +470,7 @@ services:
|
||||
- METRICS_JWT_SECRET=${JWT_SECRET}
|
||||
- REGION=local
|
||||
- ERL_AFLAGS=-proto_dist inet_tcp
|
||||
- POOLER_TENANT_ID=your-tenant-id
|
||||
- POOLER_TENANT_ID=${POOLER_TENANT_ID}
|
||||
- POOLER_DEFAULT_POOL_SIZE=${POOLER_DEFAULT_POOL_SIZE}
|
||||
- POOLER_MAX_CLIENT_CONN=${POOLER_MAX_CLIENT_CONN}
|
||||
- POOLER_POOL_MODE=transaction
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
# PostgreSQL SQL Style Guide
|
||||
|
||||
## General
|
||||
|
||||
- Use lowercase for SQL reserved words to maintain consistency and readability.
|
||||
- Employ consistent, descriptive identifiers for tables, columns, and other database objects.
|
||||
- Use white space and indentation to enhance the readability of your code.
|
||||
- Store dates in ISO 8601 format (`yyyy-mm-ddThh:mm:ss.sssss`).
|
||||
- Include comments for complex logic, using '/* ... */' for block comments and '--' for line comments.
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
- Avoid SQL reserved words and ensure names are unique and under 63 characters.
|
||||
- Use snake_case for tables and columns.
|
||||
- Prefer plurals for table names
|
||||
- Prefer singular names for columns.
|
||||
|
||||
## Tables
|
||||
|
||||
- Avoid prefixes like 'tbl_' and ensure no table name matches any of its column names.
|
||||
- Always add an `id` column of type `identity generated always` unless otherwise specified.
|
||||
- Create all tables in the `public` schema unless otherwise specified.
|
||||
- Always add the schema to SQL queries for clarity.
|
||||
- Always add a comment to describe what the table does. The comment can be up to 1024 characters.
|
||||
|
||||
## Columns
|
||||
|
||||
- Use singular names and avoid generic names like 'id'.
|
||||
- For references to foreign tables, use the singular of the table name with the `_id` suffix. For example `user_id` to reference the `users` table
|
||||
- Always use lowercase except in cases involving acronyms or when readability would be enhanced by an exception.
|
||||
|
||||
#### Examples:
|
||||
|
||||
```sql
|
||||
create table books (
|
||||
id bigint generated always as identity primary key,
|
||||
title text not null,
|
||||
author_id bigint references authors (id)
|
||||
);
|
||||
comment on table books is 'A list of all the books in the library.';
|
||||
```
|
||||
|
||||
|
||||
## Queries
|
||||
|
||||
- When the query is shorter keep it on just a few lines. As it gets larger start adding newlines for readability
|
||||
- Add spaces for readability.
|
||||
|
||||
Smaller queries:
|
||||
|
||||
|
||||
```sql
|
||||
select *
|
||||
from employees
|
||||
where end_date is null;
|
||||
|
||||
update employees
|
||||
set end_date = '2023-12-31'
|
||||
where employee_id = 1001;
|
||||
```
|
||||
|
||||
Larger queries:
|
||||
|
||||
```sql
|
||||
select
|
||||
first_name,
|
||||
last_name
|
||||
from
|
||||
employees
|
||||
where
|
||||
start_date between '2021-01-01' and '2021-12-31'
|
||||
and
|
||||
status = 'employed';
|
||||
```
|
||||
|
||||
|
||||
### Joins and Subqueries
|
||||
|
||||
- Format joins and subqueries for clarity, aligning them with related SQL clauses.
|
||||
- Prefer full table names when referencing tables. This helps for readability.
|
||||
|
||||
```sql
|
||||
select
|
||||
employees.employee_name,
|
||||
departments.department_name
|
||||
from
|
||||
employees
|
||||
join
|
||||
departments on employees.department_id = departments.department_id
|
||||
where
|
||||
employees.start_date > '2022-01-01';
|
||||
```
|
||||
|
||||
## Aliases
|
||||
|
||||
- Use meaningful aliases that reflect the data or transformation applied, and always include the 'as' keyword for clarity.
|
||||
|
||||
```sql
|
||||
select count(*) as total_employees
|
||||
from employees
|
||||
where end_date is null;
|
||||
```
|
||||
|
||||
|
||||
## Complex queries and CTEs
|
||||
|
||||
- If a query is extremely complex, prefer a CTE.
|
||||
- Make sure the CTE is clear and linear. Prefer readability over performance.
|
||||
- Add comments to each block.
|
||||
|
||||
```sql
|
||||
with department_employees as (
|
||||
-- Get all employees and their departments
|
||||
select
|
||||
employees.department_id,
|
||||
employees.first_name,
|
||||
employees.last_name,
|
||||
departments.department_name
|
||||
from
|
||||
employees
|
||||
join
|
||||
departments on employees.department_id = departments.department_id
|
||||
),
|
||||
employee_counts as (
|
||||
-- Count how many employees in each department
|
||||
select
|
||||
department_name,
|
||||
count(*) as num_employees
|
||||
from
|
||||
department_employees
|
||||
group by
|
||||
department_name
|
||||
)
|
||||
select
|
||||
department_name,
|
||||
num_employees
|
||||
from
|
||||
employee_counts
|
||||
order by
|
||||
department_name;
|
||||
```
|
||||
@@ -0,0 +1,48 @@
|
||||
<!-- SUGGESTION: include the `code-format-sql.md` prompt for coding style. -->
|
||||
|
||||
# Database: Create migration
|
||||
|
||||
You are a Postgres Expert who loves creating secure database schemas.
|
||||
|
||||
This project uses the migrations provided by the Supabase CLI.
|
||||
|
||||
## Creating a migration file
|
||||
|
||||
Given the context of the user's message, create a database migration file inside the folder `supabase/migrations/`.
|
||||
|
||||
The file MUST following this naming convention:
|
||||
|
||||
The file MUST be named in the format `YYYYMMDDHHmmss_short_description.sql` with proper casing for months, minutes, and seconds in UTC time:
|
||||
|
||||
1. `YYYY` - Four digits for the year (e.g., `2024`).
|
||||
2. `MM` - Two digits for the month (01 to 12).
|
||||
3. `DD` - Two digits for the day of the month (01 to 31).
|
||||
4. `HH` - Two digits for the hour in 24-hour format (00 to 23).
|
||||
5. `mm` - Two digits for the minute (00 to 59).
|
||||
6. `ss` - Two digits for the second (00 to 59).
|
||||
7. Add an appropriate description for the migration.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
20240906123045_create_profiles.sql
|
||||
```
|
||||
|
||||
|
||||
## SQL Guidelines
|
||||
|
||||
Write Postgres-compatible SQL code for Supabase migration files that:
|
||||
|
||||
- Includes a header comment with metadata about the migration, such as the purpose, affected tables/columns, and any special considerations.
|
||||
- Includes thorough comments explaining the purpose and expected behavior of each migration step.
|
||||
- Write all SQL in lowercase.
|
||||
- Add copious comments for any destructive SQL commands, including truncating, dropping, or column alterations.
|
||||
- When creating a new table, you MUST enable Row Level Security (RLS) even if the table is intended for public access.
|
||||
- When creating RLS Policies
|
||||
- Ensure the policies cover all relevant access scenarios (e.g. select, insert, update, delete) based on the table's purpose and data sensitivity.
|
||||
- If the table is intended for public access the policy can simply return `true`.
|
||||
- RLS Policies should be granular: one policy for `select`, one for `insert` etc) and for each supabase role (`anon` and `authenticated`). DO NOT combine Policies even if the functionality is the same for both roles.
|
||||
- Include comments explaining the rationale and intended behavior of each security policy
|
||||
|
||||
The generated SQL code should be production-ready, well-documented, and aligned with Supabase's best practices.
|
||||
|
||||
@@ -68,7 +68,7 @@ return new Response(JSON.stringify(data), {
|
||||
|
||||
---
|
||||
|
||||
[👉 Next lesson](/04-proxy-supabase-requests-with-cloudflare-workers-and-itty-router)
|
||||
[👉 Next lesson](https://github.com/dijonmusters/supabase-data-at-the-edge/tree/main/04-proxy-supabase-requests-with-cloudflare-workers-and-itty-router)
|
||||
|
||||
---
|
||||
|
||||
|
||||
Generated
+1394
-340
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user