Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions apps/docs/content/guides/functions/auth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ See the [`@supabase/server` docs](https://github.com/supabase/server) for the fu

</Admonition>

### Authenticated user calls
### Authenticated user calls [#authenticated-user-calls-with-server-sdk]

`allow: 'user'` pairs with `verify_jwt = true`. The platform validates the JWT, and the SDK hands you `ctx.supabase` already scoped to the caller.

Expand All @@ -186,7 +186,7 @@ export default {
}
```

### Service-to-service calls
### Service-to-service calls [#service-to-service-calls-with-server-sdk]

`allow: 'secret:<name>'` validates the `apikey` header against the named secret key from your [dashboard](/dashboard/project/_/settings/api-keys) and gives you `ctx.supabaseAdmin` for privileged work. The `<name>` matches the name you gave the key. Keep `verify_jwt = false`.

Expand All @@ -209,11 +209,11 @@ Create a named secret key for each caller in the [**Settings > API keys**](/dash

</Admonition>

### Public functions
### Public functions [#public-functions-with-server-sdk]

The SDK adds nothing to a truly public function. Use the raw pattern from the previous section. If you need a Supabase client anyway, `allow: 'always'` with `verify_jwt = false` skips every check and treats every caller as anonymous.

### External webhooks
### External webhooks [#external-webhooks-with-server-sdk]

Use `allow: 'always'` to skip the SDK's credential check, then verify the provider's signature inside the handler. Keep `verify_jwt = false`.

Expand Down
4 changes: 2 additions & 2 deletions apps/studio/components/interfaces/Account/AuditLogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import { FilterPopover } from '@/components/ui/FilterPopover'
import {
TIMESTAMP_MICROS_PER_MS,
type V2AuditLog,
type AuditLog,
} from '@/data/organizations/organization-audit-logs-query'
import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
import { useProfileAuditLogsQuery } from '@/data/profile/profile-audit-logs-query'
Expand All @@ -35,7 +35,7 @@ export const AuditLogs = () => {
to: currentTime.toISOString(),
})

const [selectedLog, setSelectedLog] = useState<V2AuditLog>()
const [selectedLog, setSelectedLog] = useState<AuditLog>()
const [filters, setFilters] = useState<{ projects: string[] }>({
projects: [],
})
Expand Down
6 changes: 3 additions & 3 deletions apps/studio/components/interfaces/Account/AuditLogs.utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { V2AuditLog } from '@/data/organizations/organization-audit-logs-query'
import type { AuditLog } from '@/data/organizations/organization-audit-logs-query'

export function sortAuditLogs(logs: V2AuditLog[], descending: boolean): V2AuditLog[] {
export function sortAuditLogs(logs: AuditLog[], descending: boolean): AuditLog[] {
return [...logs].sort((a, b) =>
descending ? b.timestamp - a.timestamp : a.timestamp - b.timestamp
)
}

export function filterByProjects(logs: V2AuditLog[], projectRefs: string[]): V2AuditLog[] {
export function filterByProjects(logs: AuditLog[], projectRefs: string[]): AuditLog[] {
if (projectRefs.length === 0) return logs
return logs.filter((log) => projectRefs.includes(log.project_ref ?? ''))
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import {
} from '@/components/ui/Forms/FormSection'
import {
TIMESTAMP_MICROS_PER_MS,
type V2AuditLog,
type AuditLog,
} from '@/data/organizations/organization-audit-logs-query'

interface LogDetailsPanelProps {
selectedLog?: V2AuditLog
selectedLog?: AuditLog
onClose: () => void
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,21 @@ export const PublicationSkeleton = ({ index }: PublicationSkeletonProps) => {
</TableRow>
)
}

export const PublicationTablesSkeleton = ({ index }: PublicationSkeletonProps) => {
return (
<TableRow>
<TableCell style={{ width: '35%' }}>
<ShimmeringLoader className="h-4 w-24 my-0.5 p-0" delayIndex={index} />
</TableCell>
<TableCell className="hidden lg:table-cell" style={{ width: '15%' }}>
<ShimmeringLoader className="h-4 w-14 my-0.5 p-0" delayIndex={index} />
</TableCell>
<TableCell className="px-4 py-3 pr-2">
<div className="flex">
<ShimmeringLoader className="h-6 w-12 p-0" delayIndex={index} />
</div>
</TableCell>
</TableRow>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,29 +112,27 @@ export const PublicationsList = () => {

return (
<>
<div className="mb-4">
<div className="flex items-center justify-between">
<div className="flex items-center">
<Input
ref={searchInputRef}
size="tiny"
icon={<Search />}
className="w-48"
placeholder="Search for a publication"
value={filterString}
onChange={(e) => setFilterString(e.target.value)}
onKeyDown={onSearchInputEscape(filterString, setFilterString)}
<div className="flex items-center justify-between">
<div className="flex items-center">
<Input
ref={searchInputRef}
size="tiny"
icon={<Search />}
className="w-48"
placeholder="Search for a publication"
value={filterString}
onChange={(e) => setFilterString(e.target.value)}
onKeyDown={onSearchInputEscape(filterString, setFilterString)}
/>
</div>
{isPermissionsLoaded && !canUpdatePublications && (
<div className="w-[500px]">
<InformationBox
icon={<AlertCircle className="text-foreground-light" strokeWidth={2} />}
title="You need additional permissions to update database publications"
/>
</div>
{isPermissionsLoaded && !canUpdatePublications && (
<div className="w-[500px]">
<InformationBox
icon={<AlertCircle className="text-foreground-light" strokeWidth={2} />}
title="You need additional permissions to update database publications"
/>
</div>
)}
</div>
)}
</div>

<div className="w-full overflow-hidden overflow-x-auto">
Expand Down Expand Up @@ -163,6 +161,18 @@ export const PublicationsList = () => {
</TableRow>
)}

{!isLoading && publications.length === 0 && (
<TableRow>
<TableCell colSpan={7}>
<NoSearchResults
searchString={filterString}
onResetFilter={() => setFilterString('')}
className="border-none !p-0"
/>
</TableCell>
</TableRow>
)}

{isSuccess &&
publications.map((x) => (
<TableRow key={x.name}>
Expand Down Expand Up @@ -223,14 +233,6 @@ export const PublicationsList = () => {
</Card>
</div>

{!isLoading && publications.length === 0 && (
<NoSearchResults
searchString={filterString}
onResetFilter={() => setFilterString('')}
className="rounded-t-none border-t-0"
/>
)}

<ConfirmationModal
visible={toggleListenEventValue !== null}
title={`Confirm to toggle sending ${toggleListenEventValue?.event.event.toLowerCase()} events`}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,43 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { ChevronLeft, Search } from 'lucide-react'
import Link from 'next/link'
import { useMemo, useState } from 'react'
import { Card, LogoLoader, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui'
import { Search } from 'lucide-react'
import { useMemo, useRef, useState } from 'react'
import { Card, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui'
import { Admonition } from 'ui-patterns'
import { Input } from 'ui-patterns/DataInputs/Input'

import { PublicationTablesSkeleton } from './PublicationSkeleton'
import { PublicationsTableItem } from './PublicationsTableItem'
import AlertError from '@/components/ui/AlertError'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import { AlertError } from '@/components/ui/AlertError'
import { NoSearchResults } from '@/components/ui/NoSearchResults'
import { useDatabasePublicationsQuery } from '@/data/database-publications/database-publications-query'
import { useTablesQuery } from '@/data/tables/tables-query'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { onSearchInputEscape } from '@/lib/keyboard'
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
import { useShortcut } from '@/state/shortcuts/useShortcut'

export const PublicationsTables = () => {
const { ref, id } = useParams()
const { id } = useParams()
const { data: project } = useSelectedProjectQuery()
const [filterString, setFilterString] = useState<string>('')
const searchInputRef = useRef<HTMLInputElement>(null)

const { can: canUpdatePublications, isLoading: isLoadingPermissions } = useAsyncCheckPermissions(
PermissionAction.TENANT_SQL_ADMIN_WRITE,
'publications'
)

useShortcut(
SHORTCUT_IDS.LIST_PAGE_FOCUS_SEARCH,
() => {
searchInputRef.current?.focus()
searchInputRef.current?.select()
},
{ label: 'Search publications' }
)

const { data: publications = [] } = useDatabasePublicationsQuery({
projectRef: project?.ref,
connectionString: project?.connectionString,
Expand Down Expand Up @@ -53,27 +65,16 @@ export const PublicationsTables = () => {
<>
<div className="mb-4">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<ButtonTooltip
asChild
type="outline"
icon={<ChevronLeft />}
style={{ padding: '5px' }}
tooltip={{ content: { side: 'bottom', text: 'Go back to publications list' } }}
>
<Link href={`/project/${ref}/database/publications`} />
</ButtonTooltip>
<div>
<Input
size="tiny"
placeholder="Search for a table"
value={filterString}
onChange={(e) => setFilterString(e.target.value)}
icon={<Search />}
className="w-48"
/>
</div>
</div>
<Input
size="tiny"
ref={searchInputRef}
icon={<Search />}
className="w-48"
placeholder="Search for a table"
value={filterString}
onChange={(e) => setFilterString(e.target.value)}
onKeyDown={onSearchInputEscape(filterString, setFilterString)}
/>
{!isLoadingPermissions && !canUpdatePublications && (
<Admonition
type="note"
Expand All @@ -84,55 +85,69 @@ export const PublicationsTables = () => {
</div>
</div>

{(isLoading || isLoadingPermissions) && (
<div className="mt-8">
<LogoLoader />
</div>
)}
<Card>
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Schema</TableHead>
<TableHead className="hidden lg:table-cell">Description</TableHead>
{/*
We've disabled All tables toggle for publications.
See https://github.com/supabase/supabase/pull/7233.
*/}
<TableHead />
</TableRow>
</TableHeader>
<TableBody>
{(isLoading || isLoadingPermissions) &&
Array.from({ length: 2 }).map((_, i) => (
<PublicationTablesSkeleton key={i} index={i} />
))}

{isError && (
<TableRow>
<TableCell colSpan={4}>
<AlertError error={error} subject="Failed to retrieve tables" />
</TableCell>
</TableRow>
)}

{isError && <AlertError error={error} subject="Failed to retrieve tables" />}
{!isLoading && !isLoadingPermissions && tables.length === 0 && (
<TableRow>
<TableCell colSpan={4}>
<NoSearchResults
className="border-none !p-0"
searchString={filterString}
onResetFilter={() => setFilterString('')}
/>
</TableCell>
</TableRow>
)}

{isSuccess &&
(tables.length === 0 ? (
<NoSearchResults searchString={filterString} onResetFilter={() => setFilterString('')} />
) : (
<Card>
<Table>
<TableHeader>
{isSuccess ? (
!!selectedPublication ? (
tables.map((table) => (
<PublicationsTableItem
key={table.id}
table={table}
selectedPublication={selectedPublication}
/>
))
) : (
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Schema</TableHead>
<TableHead className="hidden lg:table-cell">Description</TableHead>
{/*
We've disabled All tables toggle for publications.
See https://github.com/supabase/supabase/pull/7233.
*/}
<TableHead />
<TableCell colSpan={4}>
<p>The selected publication with ID {id} cannot be found</p>
<p className="text-foreground-light">
Head back to the list of publications to select one from there
</p>
</TableCell>
</TableRow>
</TableHeader>
<TableBody>
{!!selectedPublication ? (
tables.map((table) => (
<PublicationsTableItem
key={table.id}
table={table}
selectedPublication={selectedPublication}
/>
))
) : (
<TableRow>
<TableCell colSpan={4}>
<p>The selected publication with ID {id} cannot be found</p>
<p className="text-foreground-light">
Head back to the list of publications to select one from there
</p>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</Card>
))}
)
) : null}
</TableBody>
</Table>
</Card>
</>
)
}
Loading
Loading