Merge branch 'master' into feat/logflare-workflow-run-logs

This commit is contained in:
Alaister Young committed 2024-10-18 15:45:05 +08:00
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.
+2 -1
View File
@@ -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/
+1 -1
View File
@@ -1497,7 +1497,7 @@ functions:
code: |
```c#
var result = await supabase.From<City>()
.Where(x => x.Name == null
.Where(x => x.Name == null)
.Get();
```
@@ -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)
@@ -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(
+13
View File
@@ -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
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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",
+17
View File
@@ -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
+11 -2
View File
@@ -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),
+9
View File
@@ -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,
})
+9
View File
@@ -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',
+8 -9
View File
@@ -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()
+1
View File
@@ -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
############
-2
View File
@@ -1,5 +1,3 @@
version: "3.8"
services:
minio:
+3 -3
View File
@@ -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
+141
View File
@@ -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.
+1 -1
View File
@@ -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)
---
+1394 -340
View File
File diff suppressed because it is too large. Load diff