Refactor Supabase server client and improve team management features
Refactored Supabase server component client to improve type safety. Improved team account management by adjusting role data providers to consider account IDs, and adjusted invite member functionality to use account IDs and slugs. Enhanced invitation update dialog by adding account parameter. Fixed database webhook URLs in seed.sql. Overall, these changes enhance the robustness and usability of the team management functionalities.
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import { ExclamationTriangleIcon } from '@radix-ui/react-icons';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BillingPortalCard,
|
BillingPortalCard,
|
||||||
CurrentSubscriptionCard,
|
CurrentSubscriptionCard,
|
||||||
@@ -39,6 +41,31 @@ async function TeamAccountBillingPage({ params }: Params) {
|
|||||||
const canManageBilling =
|
const canManageBilling =
|
||||||
workspace.account.permissions.includes('billing.manage');
|
workspace.account.permissions.includes('billing.manage');
|
||||||
|
|
||||||
|
const Checkout = () => {
|
||||||
|
if (!canManageBilling) {
|
||||||
|
return <CannotManageBillingAlert />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TeamAccountCheckoutForm customerId={customerId} accountId={accountId} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const BillingPortal = () => {
|
||||||
|
if (!canManageBilling || !customerId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form action={createBillingPortalSession}>
|
||||||
|
<input type="hidden" name={'accountId'} value={accountId} />
|
||||||
|
<input type="hidden" name={'slug'} value={params.account} />
|
||||||
|
|
||||||
|
<BillingPortalCard />
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
@@ -48,39 +75,25 @@ async function TeamAccountBillingPage({ params }: Params) {
|
|||||||
|
|
||||||
<PageBody>
|
<PageBody>
|
||||||
<div className={'mx-auto w-full'}>
|
<div className={'mx-auto w-full'}>
|
||||||
<If condition={!canManageBilling}>
|
|
||||||
<CannotManageBillingAlert />
|
|
||||||
</If>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className={'flex flex-col space-y-6'}>
|
<div className={'flex flex-col space-y-6'}>
|
||||||
<If
|
<If
|
||||||
condition={subscription}
|
condition={subscription}
|
||||||
fallback={
|
fallback={
|
||||||
<If condition={canManageBilling}>
|
<>
|
||||||
<TeamAccountCheckoutForm
|
<Checkout />
|
||||||
customerId={customerId}
|
</>
|
||||||
accountId={accountId}
|
|
||||||
/>
|
|
||||||
</If>
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{(data) => (
|
{(subscription) => (
|
||||||
<CurrentSubscriptionCard
|
<CurrentSubscriptionCard
|
||||||
subscription={data}
|
subscription={subscription}
|
||||||
config={billingConfig}
|
config={billingConfig}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
<If condition={customerId && canManageBilling}>
|
<BillingPortal />
|
||||||
<form action={createBillingPortalSession}>
|
|
||||||
<input type="hidden" name={'accountId'} value={accountId} />
|
|
||||||
<input type="hidden" name={'slug'} value={params.account} />
|
|
||||||
|
|
||||||
<BillingPortalCard />
|
|
||||||
</form>
|
|
||||||
</If>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,7 +106,9 @@ export default withI18n(TeamAccountBillingPage);
|
|||||||
|
|
||||||
function CannotManageBillingAlert() {
|
function CannotManageBillingAlert() {
|
||||||
return (
|
return (
|
||||||
<Alert>
|
<Alert variant={'warning'}>
|
||||||
|
<ExclamationTriangleIcon className={'h-4'} />
|
||||||
|
|
||||||
<AlertTitle>
|
<AlertTitle>
|
||||||
<Trans i18nKey={'billing:cannotManageBillingAlertTitle'} />
|
<Trans i18nKey={'billing:cannotManageBillingAlertTitle'} />
|
||||||
</AlertTitle>
|
</AlertTitle>
|
||||||
|
|||||||
@@ -131,10 +131,12 @@ async function TeamAccountMembersPage({ params }: Params) {
|
|||||||
<If condition={canManageInvitations}>
|
<If condition={canManageInvitations}>
|
||||||
<InviteMembersDialogContainer
|
<InviteMembersDialogContainer
|
||||||
userRoleHierarchy={currentUserRoleHierarchy}
|
userRoleHierarchy={currentUserRoleHierarchy}
|
||||||
account={account.slug}
|
accountId={account.id}
|
||||||
|
accountSlug={account.slug}
|
||||||
>
|
>
|
||||||
<Button size={'sm'}>
|
<Button size={'sm'}>
|
||||||
<PlusCircle className={'mr-2 w-4'} />
|
<PlusCircle className={'mr-2 w-4'} />
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
<Trans i18nKey={'teams:inviteMembersButton'} />
|
<Trans i18nKey={'teams:inviteMembersButton'} />
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -197,6 +197,7 @@ function ActionsDropdown({
|
|||||||
<UpdateInvitationDialog
|
<UpdateInvitationDialog
|
||||||
isOpen
|
isOpen
|
||||||
setIsOpen={setIsUpdatingRole}
|
setIsOpen={setIsUpdatingRole}
|
||||||
|
account={invitation.account_id}
|
||||||
invitationId={invitation.id}
|
invitationId={invitation.id}
|
||||||
userRole={invitation.role}
|
userRole={invitation.role}
|
||||||
userRoleHierarchy={permissions.currentUserRoleHierarchy}
|
userRoleHierarchy={permissions.currentUserRoleHierarchy}
|
||||||
|
|||||||
@@ -36,9 +36,17 @@ export const UpdateInvitationDialog: React.FC<{
|
|||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
setIsOpen: (isOpen: boolean) => void;
|
setIsOpen: (isOpen: boolean) => void;
|
||||||
invitationId: number;
|
invitationId: number;
|
||||||
|
account: string;
|
||||||
userRole: Role;
|
userRole: Role;
|
||||||
userRoleHierarchy: number;
|
userRoleHierarchy: number;
|
||||||
}> = ({ isOpen, setIsOpen, invitationId, userRole, userRoleHierarchy }) => {
|
}> = ({
|
||||||
|
isOpen,
|
||||||
|
setIsOpen,
|
||||||
|
invitationId,
|
||||||
|
userRole,
|
||||||
|
userRoleHierarchy,
|
||||||
|
account,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
@@ -52,9 +60,13 @@ export const UpdateInvitationDialog: React.FC<{
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<RolesDataProvider maxRoleHierarchy={userRoleHierarchy}>
|
<RolesDataProvider
|
||||||
|
accountId={account}
|
||||||
|
maxRoleHierarchy={userRoleHierarchy}
|
||||||
|
>
|
||||||
{(roles) => (
|
{(roles) => (
|
||||||
<UpdateInvitationForm
|
<UpdateInvitationForm
|
||||||
|
account={account}
|
||||||
invitationId={invitationId}
|
invitationId={invitationId}
|
||||||
userRole={userRole}
|
userRole={userRole}
|
||||||
userRoleHierarchy={roles.length}
|
userRoleHierarchy={roles.length}
|
||||||
@@ -68,11 +80,13 @@ export const UpdateInvitationDialog: React.FC<{
|
|||||||
};
|
};
|
||||||
|
|
||||||
function UpdateInvitationForm({
|
function UpdateInvitationForm({
|
||||||
|
account,
|
||||||
invitationId,
|
invitationId,
|
||||||
userRole,
|
userRole,
|
||||||
userRoleHierarchy,
|
userRoleHierarchy,
|
||||||
setIsOpen,
|
setIsOpen,
|
||||||
}: React.PropsWithChildren<{
|
}: React.PropsWithChildren<{
|
||||||
|
account: string;
|
||||||
invitationId: number;
|
invitationId: number;
|
||||||
userRole: Role;
|
userRole: Role;
|
||||||
userRoleHierarchy: number;
|
userRoleHierarchy: number;
|
||||||
@@ -136,7 +150,10 @@ function UpdateInvitationForm({
|
|||||||
</FormLabel>
|
</FormLabel>
|
||||||
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<RolesDataProvider maxRoleHierarchy={userRoleHierarchy}>
|
<RolesDataProvider
|
||||||
|
accountId={account}
|
||||||
|
maxRoleHierarchy={userRoleHierarchy}
|
||||||
|
>
|
||||||
{(roles) => (
|
{(roles) => (
|
||||||
<MembershipRoleSelector
|
<MembershipRoleSelector
|
||||||
roles={roles}
|
roles={roles}
|
||||||
|
|||||||
@@ -42,11 +42,13 @@ type InviteModel = ReturnType<typeof createEmptyInviteModel>;
|
|||||||
type Role = string;
|
type Role = string;
|
||||||
|
|
||||||
export function InviteMembersDialogContainer({
|
export function InviteMembersDialogContainer({
|
||||||
account,
|
accountSlug,
|
||||||
|
accountId,
|
||||||
userRoleHierarchy,
|
userRoleHierarchy,
|
||||||
children,
|
children,
|
||||||
}: React.PropsWithChildren<{
|
}: React.PropsWithChildren<{
|
||||||
account: string;
|
accountSlug: string;
|
||||||
|
accountId: string;
|
||||||
userRoleHierarchy: number;
|
userRoleHierarchy: number;
|
||||||
}>) {
|
}>) {
|
||||||
const [pending, startTransition] = useTransition();
|
const [pending, startTransition] = useTransition();
|
||||||
@@ -67,7 +69,10 @@ export function InviteMembersDialogContainer({
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<RolesDataProvider maxRoleHierarchy={userRoleHierarchy}>
|
<RolesDataProvider
|
||||||
|
accountId={accountId}
|
||||||
|
maxRoleHierarchy={userRoleHierarchy}
|
||||||
|
>
|
||||||
{(roles) => (
|
{(roles) => (
|
||||||
<InviteMembersForm
|
<InviteMembersForm
|
||||||
pending={pending}
|
pending={pending}
|
||||||
@@ -75,7 +80,7 @@ export function InviteMembersDialogContainer({
|
|||||||
onSubmit={(data) => {
|
onSubmit={(data) => {
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
await createInvitationsAction({
|
await createInvitationsAction({
|
||||||
account,
|
accountSlug,
|
||||||
invitations: data.invitations,
|
invitations: data.invitations,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,17 +5,15 @@ import { LoadingOverlay } from '@kit/ui/loading-overlay';
|
|||||||
|
|
||||||
export function RolesDataProvider(props: {
|
export function RolesDataProvider(props: {
|
||||||
maxRoleHierarchy: number;
|
maxRoleHierarchy: number;
|
||||||
|
accountId: string;
|
||||||
children: (roles: string[]) => React.ReactNode;
|
children: (roles: string[]) => React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const rolesQuery = useFetchRoles({
|
const rolesQuery = useFetchRoles(props);
|
||||||
maxRoleHierarchy: props.maxRoleHierarchy,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (rolesQuery.isLoading) {
|
if (rolesQuery.isLoading) {
|
||||||
return <LoadingOverlay fullPage={false} />;
|
return <LoadingOverlay fullPage={false} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO handle error
|
|
||||||
if (rolesQuery.isError) {
|
if (rolesQuery.isError) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -23,7 +21,7 @@ export function RolesDataProvider(props: {
|
|||||||
return <>{props.children(rolesQuery.data ?? [])}</>;
|
return <>{props.children(rolesQuery.data ?? [])}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useFetchRoles(props: { maxRoleHierarchy: number }) {
|
function useFetchRoles(props: { maxRoleHierarchy: number; accountId: string }) {
|
||||||
const supabase = useSupabase();
|
const supabase = useSupabase();
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -33,6 +31,7 @@ function useFetchRoles(props: { maxRoleHierarchy: number }) {
|
|||||||
.from('roles')
|
.from('roles')
|
||||||
.select('name')
|
.select('name')
|
||||||
.gte('hierarchy_level', props.maxRoleHierarchy)
|
.gte('hierarchy_level', props.maxRoleHierarchy)
|
||||||
|
.or(`account_id.eq.${props.accountId}, account_id.is.null`)
|
||||||
.order('hierarchy_level', { ascending: true });
|
.order('hierarchy_level', { ascending: true });
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|||||||
@@ -60,7 +60,10 @@ export const UpdateMemberRoleDialog: React.FC<{
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<RolesDataProvider maxRoleHierarchy={userRoleHierarchy}>
|
<RolesDataProvider
|
||||||
|
accountId={accountId}
|
||||||
|
maxRoleHierarchy={userRoleHierarchy}
|
||||||
|
>
|
||||||
{(data) => (
|
{(data) => (
|
||||||
<UpdateMemberForm
|
<UpdateMemberForm
|
||||||
setIsOpen={setIsOpen}
|
setIsOpen={setIsOpen}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { AccountInvitationsService } from '../services/account-invitations.servi
|
|||||||
* Creates invitations for inviting members.
|
* Creates invitations for inviting members.
|
||||||
*/
|
*/
|
||||||
export async function createInvitationsAction(params: {
|
export async function createInvitationsAction(params: {
|
||||||
account: string;
|
accountSlug: string;
|
||||||
invitations: z.infer<typeof InviteMembersSchema>['invitations'];
|
invitations: z.infer<typeof InviteMembersSchema>['invitations'];
|
||||||
}) {
|
}) {
|
||||||
const client = getSupabaseServerActionClient();
|
const client = getSupabaseServerActionClient();
|
||||||
@@ -35,7 +35,10 @@ export async function createInvitationsAction(params: {
|
|||||||
|
|
||||||
const service = new AccountInvitationsService(client);
|
const service = new AccountInvitationsService(client);
|
||||||
|
|
||||||
await service.sendInvitations({ invitations, account: params.account });
|
await service.sendInvitations({
|
||||||
|
invitations,
|
||||||
|
accountSlug: params.accountSlug,
|
||||||
|
});
|
||||||
|
|
||||||
revalidateMemberPage();
|
revalidateMemberPage();
|
||||||
|
|
||||||
|
|||||||
@@ -69,21 +69,21 @@ export class AccountInvitationsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async sendInvitations({
|
async sendInvitations({
|
||||||
account,
|
accountSlug,
|
||||||
invitations,
|
invitations,
|
||||||
}: {
|
}: {
|
||||||
invitations: z.infer<typeof InviteMembersSchema>['invitations'];
|
invitations: z.infer<typeof InviteMembersSchema>['invitations'];
|
||||||
account: string;
|
accountSlug: string;
|
||||||
}) {
|
}) {
|
||||||
Logger.info(
|
Logger.info(
|
||||||
{ account, invitations, name: this.namespace },
|
{ account: accountSlug, invitations, name: this.namespace },
|
||||||
'Storing invitations',
|
'Storing invitations',
|
||||||
);
|
);
|
||||||
|
|
||||||
const accountResponse = await this.client
|
const accountResponse = await this.client
|
||||||
.from('accounts')
|
.from('accounts')
|
||||||
.select('name')
|
.select('name')
|
||||||
.eq('slug', account)
|
.eq('slug', accountSlug)
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (!accountResponse.data) {
|
if (!accountResponse.data) {
|
||||||
@@ -92,7 +92,7 @@ export class AccountInvitationsService {
|
|||||||
|
|
||||||
const response = await this.client.rpc('add_invitations_to_account', {
|
const response = await this.client.rpc('add_invitations_to_account', {
|
||||||
invitations,
|
invitations,
|
||||||
account_slug: account,
|
account_slug: accountSlug,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
@@ -105,7 +105,7 @@ export class AccountInvitationsService {
|
|||||||
|
|
||||||
Logger.info(
|
Logger.info(
|
||||||
{
|
{
|
||||||
account,
|
account: accountSlug,
|
||||||
count: responseInvitations.length,
|
count: responseInvitations.length,
|
||||||
name: this.namespace,
|
name: this.namespace,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { getSupabaseClientKeys } from '../get-supabase-client-keys';
|
|||||||
* @name getSupabaseServerComponentClient
|
* @name getSupabaseServerComponentClient
|
||||||
* @description Get a Supabase client for use in the Server Components
|
* @description Get a Supabase client for use in the Server Components
|
||||||
*/
|
*/
|
||||||
export const getSupabaseServerComponentClient = <GenericSchema = Database>(
|
export const getSupabaseServerComponentClient = (
|
||||||
params = {
|
params = {
|
||||||
admin: false,
|
admin: false,
|
||||||
},
|
},
|
||||||
@@ -30,7 +30,7 @@ export const getSupabaseServerComponentClient = <GenericSchema = Database>(
|
|||||||
throw new Error('Supabase Service Role Key not provided');
|
throw new Error('Supabase Service Role Key not provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
return createServerClient<GenericSchema>(keys.url, serviceRoleKey, {
|
return createServerClient<Database>(keys.url, serviceRoleKey, {
|
||||||
auth: {
|
auth: {
|
||||||
persistSession: false,
|
persistSession: false,
|
||||||
},
|
},
|
||||||
@@ -38,7 +38,7 @@ export const getSupabaseServerComponentClient = <GenericSchema = Database>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return createServerClient<GenericSchema>(keys.url, keys.anonKey, {
|
return createServerClient<Database>(keys.url, keys.anonKey, {
|
||||||
cookies: getCookiesStrategy(),
|
cookies: getCookiesStrategy(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -463,7 +463,6 @@ create trigger "on_auth_user_updated"
|
|||||||
after update of email on auth.users for each row
|
after update of email on auth.users for each row
|
||||||
execute procedure kit.handle_update_user_email();
|
execute procedure kit.handle_update_user_email();
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* -------------------------------------------------------
|
* -------------------------------------------------------
|
||||||
* Section: Roles
|
* Section: Roles
|
||||||
@@ -475,13 +474,51 @@ create table if not exists public.roles(
|
|||||||
name varchar(50) not null,
|
name varchar(50) not null,
|
||||||
hierarchy_level int not null,
|
hierarchy_level int not null,
|
||||||
account_id uuid references public.accounts(id) on delete cascade,
|
account_id uuid references public.accounts(id) on delete cascade,
|
||||||
is_custom boolean not null default false,
|
unique(name, account_id),
|
||||||
unique (hierarchy_level, account_id, is_custom),
|
|
||||||
primary key (name)
|
primary key (name)
|
||||||
);
|
);
|
||||||
|
|
||||||
grant select on table public.roles to authenticated, service_role;
|
grant select on table public.roles to authenticated, service_role;
|
||||||
|
|
||||||
|
-- define the system role uuid as a static UUID to be used as a default
|
||||||
|
-- account_id for system roles when the account_id is null. Useful for constraints.
|
||||||
|
create or replace function kit.get_system_role_uuid()
|
||||||
|
returns uuid
|
||||||
|
as $$
|
||||||
|
begin
|
||||||
|
return 'fd4f287c-762e-42b7-8207-b1252f799670';
|
||||||
|
end; $$ language plpgsql immutable;
|
||||||
|
|
||||||
|
grant execute on function kit.get_system_role_uuid() to authenticated, service_role;
|
||||||
|
|
||||||
|
create unique index idx_unique_hierarchy_per_account
|
||||||
|
on public.roles (hierarchy_level, coalesce(account_id, kit.get_system_role_uuid()));
|
||||||
|
|
||||||
|
create unique index idx_unique_name_per_account
|
||||||
|
on public.roles (name, coalesce(account_id, kit.get_system_role_uuid()));
|
||||||
|
|
||||||
|
create or replace function kit.check_non_personal_account_roles()
|
||||||
|
returns trigger
|
||||||
|
as $$
|
||||||
|
begin
|
||||||
|
if new.account_id is not null and(
|
||||||
|
select
|
||||||
|
is_personal_account
|
||||||
|
from
|
||||||
|
public.accounts
|
||||||
|
where
|
||||||
|
id = new.account_id) then
|
||||||
|
raise exception 'Roles cannot be created for personal accounts';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
return new;
|
||||||
|
end; $$ language plpgsql;
|
||||||
|
|
||||||
|
create constraint trigger tr_check_non_personal_account_roles
|
||||||
|
after insert or update on public.roles
|
||||||
|
for each row
|
||||||
|
execute procedure kit.check_non_personal_account_roles();
|
||||||
|
|
||||||
-- Seed the roles table with default roles 'owner' and
|
-- Seed the roles table with default roles 'owner' and
|
||||||
-- 'member'
|
-- 'member'
|
||||||
insert into public.roles(
|
insert into public.roles(
|
||||||
@@ -501,12 +538,6 @@ values (
|
|||||||
-- RLS
|
-- RLS
|
||||||
alter table public.roles enable row level security;
|
alter table public.roles enable row level security;
|
||||||
|
|
||||||
-- SELECT: authenticated users can query roles
|
|
||||||
create policy roles_read on public.roles
|
|
||||||
for select to authenticated
|
|
||||||
using (true);
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* -------------------------------------------------------
|
* -------------------------------------------------------
|
||||||
* Section: Memberships
|
* Section: Memberships
|
||||||
@@ -567,6 +598,7 @@ create or replace trigger prevent_account_owner_membership_delete_check
|
|||||||
before delete on public.accounts_memberships for each row
|
before delete on public.accounts_memberships for each row
|
||||||
execute function kit.prevent_account_owner_membership_delete();
|
execute function kit.prevent_account_owner_membership_delete();
|
||||||
|
|
||||||
|
-- Functions
|
||||||
create or replace function public.has_role_on_account(account_id
|
create or replace function public.has_role_on_account(account_id
|
||||||
uuid, account_role varchar(50) default null)
|
uuid, account_role varchar(50) default null)
|
||||||
returns boolean
|
returns boolean
|
||||||
@@ -590,6 +622,7 @@ $$;
|
|||||||
grant execute on function public.has_role_on_account(uuid, varchar)
|
grant execute on function public.has_role_on_account(uuid, varchar)
|
||||||
to authenticated;
|
to authenticated;
|
||||||
|
|
||||||
|
-- Function to check if a user is a team member of an account or not
|
||||||
create or replace function public.is_team_member(account_id uuid,
|
create or replace function public.is_team_member(account_id uuid,
|
||||||
user_id uuid)
|
user_id uuid)
|
||||||
returns boolean
|
returns boolean
|
||||||
@@ -611,7 +644,16 @@ $$;
|
|||||||
|
|
||||||
grant execute on function public.is_team_member(uuid, uuid) to authenticated;
|
grant execute on function public.is_team_member(uuid, uuid) to authenticated;
|
||||||
|
|
||||||
-- Functions
|
|
||||||
|
-- SELECT(roles): authenticated users can query roles if the role is public
|
||||||
|
-- or the user has a role on the account the role is for
|
||||||
|
create policy roles_read on public.roles
|
||||||
|
for select to authenticated
|
||||||
|
using (
|
||||||
|
account_id is null
|
||||||
|
or public.has_role_on_account(account_id)
|
||||||
|
);
|
||||||
|
|
||||||
-- Function to check if a user can remove a member from an account
|
-- Function to check if a user can remove a member from an account
|
||||||
create or replace function
|
create or replace function
|
||||||
kit.can_remove_account_member(target_team_account_id uuid,
|
kit.can_remove_account_member(target_team_account_id uuid,
|
||||||
@@ -716,7 +758,6 @@ create policy accounts_team_read on public.accounts
|
|||||||
where
|
where
|
||||||
public.is_team_member(membership.account_id, id)));
|
public.is_team_member(membership.account_id, id)));
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* -------------------------------------------------------
|
* -------------------------------------------------------
|
||||||
* Section: Account Roles
|
* Section: Account Roles
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
create trigger "accounts_memberships_insert" after insert
|
create trigger "accounts_memberships_insert" after insert
|
||||||
on "public"."accounts_memberships" for each row
|
on "public"."accounts_memberships" for each row
|
||||||
execute function "supabase_functions"."http_request"(
|
execute function "supabase_functions"."http_request"(
|
||||||
'http://host.docker.internal:3000/api/database/webhook',
|
'http://host.docker.internal:3000/api/db/webhook',
|
||||||
'POST',
|
'POST',
|
||||||
'{"Content-Type":"application/json", "X-Supabase-Event-Signature":"WEBHOOKSECRET"}',
|
'{"Content-Type":"application/json", "X-Supabase-Event-Signature":"WEBHOOKSECRET"}',
|
||||||
'{}',
|
'{}',
|
||||||
@@ -17,7 +17,7 @@ execute function "supabase_functions"."http_request"(
|
|||||||
create trigger "account_membership_delete" after delete
|
create trigger "account_membership_delete" after delete
|
||||||
on "public"."accounts_memberships" for each row
|
on "public"."accounts_memberships" for each row
|
||||||
execute function "supabase_functions"."http_request"(
|
execute function "supabase_functions"."http_request"(
|
||||||
'http://host.docker.internal:3000/api/database/webhook',
|
'http://host.docker.internal:3000/api/db/webhook',
|
||||||
'POST',
|
'POST',
|
||||||
'{"Content-Type":"application/json", "X-Supabase-Event-Signature":"WEBHOOKSECRET"}',
|
'{"Content-Type":"application/json", "X-Supabase-Event-Signature":"WEBHOOKSECRET"}',
|
||||||
'{}',
|
'{}',
|
||||||
@@ -29,7 +29,7 @@ execute function "supabase_functions"."http_request"(
|
|||||||
create trigger "account_delete" after delete
|
create trigger "account_delete" after delete
|
||||||
on "public"."subscriptions" for each row
|
on "public"."subscriptions" for each row
|
||||||
execute function "supabase_functions"."http_request"(
|
execute function "supabase_functions"."http_request"(
|
||||||
'http://host.docker.internal:3000/api/database/webhook',
|
'http://host.docker.internal:3000/api/db/webhook',
|
||||||
'POST',
|
'POST',
|
||||||
'{"Content-Type":"application/json", "X-Supabase-Event-Signature":"WEBHOOKSECRET"}',
|
'{"Content-Type":"application/json", "X-Supabase-Event-Signature":"WEBHOOKSECRET"}',
|
||||||
'{}',
|
'{}',
|
||||||
@@ -41,7 +41,7 @@ execute function "supabase_functions"."http_request"(
|
|||||||
create trigger "invitations_insert" after insert
|
create trigger "invitations_insert" after insert
|
||||||
on "public"."invitations" for each row
|
on "public"."invitations" for each row
|
||||||
execute function "supabase_functions"."http_request"(
|
execute function "supabase_functions"."http_request"(
|
||||||
'http://host.docker.internal:3000/api/database/webhook',
|
'http://host.docker.internal:3000/api/db/webhook',
|
||||||
'POST',
|
'POST',
|
||||||
'{"Content-Type":"application/json", "X-Supabase-Event-Signature":"WEBHOOKSECRET"}',
|
'{"Content-Type":"application/json", "X-Supabase-Event-Signature":"WEBHOOKSECRET"}',
|
||||||
'{}',
|
'{}',
|
||||||
|
|||||||
Reference in New Issue
Block a user